Skip to main content
Define and version 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
  • A/B test different implementations
  • Organize complex JavaScript/TypeScript code separately from YAML
  • Deploy scripts independently from ensembles
  • Collaborate by sharing utility functions across teams

Quick Start

1. Create a Script Component

Create a JavaScript/TypeScript file that exports a default function:
// scripts/transform-data.js
export default async function(context) {
  const data = context.input.raw_data;

  // Process the input data
  const processed = 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. Add to Edgit

edgit components add transform-data scripts/transform-data.js script
edgit tag create transform-data v1.0.0
edgit deploy set transform-data v1.0.0 --to production

3. Reference in Your Ensemble

ensemble: data-processor

agents:
  - name: transform
    operation: code
    config:
      script: "script://transform-data@v1.0.0"

inputs:
  raw_data:
    type: array

outputs:
  result: ${transform.output}

URI Format and Versioning

All script components use the standardized URI format:
script://{path}[@{version}]
Format breakdown:
  • script:// - Protocol identifier for script components
  • {path} - Logical path to the script (e.g., transform-data, utils/validation)
  • [@{version}] - Optional version identifier (defaults to @latest)
Version format:
  • @latest - Always uses the most recent version
  • @v1 - Uses latest patch of major version (v1.x.x)
  • @v1.0.0 - Specific semantic version (immutable)
  • @prod - Custom tag for production versions
  • @staging - Custom tag for staging versions

Example URIs

# Always latest version
script: "script://transform-data"
script: "script://transform-data@latest"

# Specific semantic version
script: "script://transform-data@v1.0.0"
script: "script://transform-data@v2.1.3"

# Major/minor version (gets latest patch)
script: "script://transform-data@v1"
script: "script://transform-data@v1.2"

# Custom tags
script: "script://transform-data@prod"
script: "script://transform-data@staging"

# Nested paths
script: "script://utils/validation@v1.0.0"
script: "script://pipelines/etl/normalize@v2"

Script Format

Scripts must export a default function that receives the agent execution context:
// scripts/example.js
export default async function(context) {
  // Available in context:
  const {
    input,      // Input data from ensemble
    env,        // Environment variables and bindings (KV, D1, etc.)
    state,      // Shared state (if configured)
    cache,      // Cache instance
    logger,     // Logger instance
    agents      // Results from previous agents
  } = context;

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

  // Return value becomes agent output
  return result;
}

function processData(data) {
  // Helper functions can be defined in the same file
  return data;
}

TypeScript Support

// scripts/typed-transform.ts
import type { AgentExecutionContext } from '@ensemble/conductor';

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

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

export default async function(
  context: AgentExecutionContext
): Promise<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
  };
}

How to Reference in Ensembles

There are three ways to reference scripts in your ensembles: Use the script:// URI format to reference versioned script components:
ensemble: data-processor

agents:
  - name: transform
    operation: code
    config:
      script: "script://transform-data@v1.0.0"

inputs:
  raw_data:
    type: array

outputs:
  result: ${transform.output}

2. Template Expression Format

Use ${components.script_name@version} to embed script references in code:
ensemble: data-pipeline

agents:
  - name: process
    operation: code
    config:
      code: |
        // Load reusable script logic
        ${components.transform-data@v1}

        // Execute with custom input
        return transformData(input.raw_data);

inputs:
  raw_data:
    type: array

3. Inline Code

For simple operations or during development, use inline code directly:
ensemble: data-transformer

agents:
  - name: transform
    operation: code
    config:
      code: |
        // Process the input data
        const data = input.raw_data;

        return {
          processed: data.map(item => ({
            id: item.id,
            normalized_value: (item.value - item.min) / (item.max - item.min),
            timestamp: new Date(item.created_at).toISOString()
          })),
          total_items: data.length
        };

inputs:
  raw_data:
    type: array

outputs:
  result: ${transform.output}

Using Script Components

With Caching Options

agents:
  - name: transform
    operation: code
    config:
      script: "script://transform-data@v1.0.0"
      cache:
        ttl: 7200  # Cache script for 2 hours
        # or bypass: true to skip cache

Multiple Scripts in Workflow

ensemble: data-pipeline

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

agents:
  - name: extract
    operation: code
    config:
      script: "script://pipelines/extract@v1"

  - name: transform
    operation: code
    config:
      script: "script://pipelines/transform@v1"

  - name: validate
    operation: code
    config:
      script: "script://pipelines/validate@v1"

  - name: aggregate
    operation: code
    config:
      script: "script://pipelines/aggregate@v1"

Caching and Performance

Script components are automatically cached for 1 hour (3600 seconds) after first load.

Default Caching

agents:
  - name: process
    operation: code
    config:
      script: "script://transform-data@v1.0.0"
      # Cached for 1 hour automatically
Performance:
  • First load: Fetched from KV and compiled (~10-20ms)
  • Subsequent loads: Served from edge cache (~0.1ms)
  • Cache per version: Each version cached independently

Custom Cache TTL

agents:
  - name: process
    operation: code
    config:
      script: "script://transform-data@v1"
      cache:
        ttl: 86400  # 24 hours for stable scripts

Bypass Cache

agents:
  - name: process
    operation: code
    config:
      script: "script://transform-data@latest"
      cache:
        bypass: true  # Fresh load during development

Code Operation Patterns

Code agents enable you to execute JavaScript/TypeScript logic within ensembles. They’re useful for:
  • Data transformation - Processing and normalizing data
  • Business logic - Custom calculations and rules
  • Integration - Connecting multiple data sources
  • Validation - Checking data quality and constraints

Basic Code Agent

ensemble: process-order

agents:
  - name: validate
    operation: code
    config:
      code: |
        const order = input.order;

        // Validate required fields
        if (!order.customer_id || !order.items || order.items.length === 0) {
          throw new Error('Invalid order: missing required fields');
        }

        // Calculate total
        const total = order.items.reduce((sum, item) => {
          return sum + (item.price * item.quantity);
        }, 0);

        return {
          valid: true,
          order_id: order.id,
          total: total,
          item_count: order.items.length
        };

inputs:
  order:
    type: object

outputs:
  validation_result: ${validate.output}

Multi-Step Code Workflow

ensemble: data-pipeline

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

agents:
  - name: extract
    operation: code
    config:
      code: |
        // Extract relevant fields from raw input
        return input.data.map(item => ({
          id: item['ID'],
          name: item['Full Name'],
          amount: parseFloat(item['Amount'])
        }));

  - name: transform
    operation: code
    config:
      code: |
        // Normalize and enhance data
        return input.extract_output.map(item => ({
          ...item,
          amount_cents: Math.round(item.amount * 100),
          processed_at: new Date().toISOString(),
          normalized_name: item.name.toUpperCase().trim()
        }));

  - name: validate
    operation: code
    config:
      code: |
        // Validate data quality
        const items = input.transform_output;
        const valid = items.filter(item => {
          return item.id && item.amount_cents > 0;
        });

        const invalid = items.filter(item => {
          return !item.id || item.amount_cents <= 0;
        });

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

  - name: aggregate
    operation: code
    config:
      code: |
        // Aggregate results
        const items = input.validate_output.valid_items;

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

inputs:
  data:
    type: array

outputs:
  result: ${aggregate.output}

Best Practices (Today)

1. Keep Code Modular

Break complex logic into multiple code agents:
ensemble: complex-workflow

flow:
  - agent: step1
  - agent: step2
  - agent: step3

agents:
  - name: step1
    operation: code
    config:
      code: |
        return { step1_result: input.data };

  - name: step2
    operation: code
    config:
      code: |
        const result = input.step1_result;
        return { step2_result: result };

  - name: step3
    operation: code
    config:
      code: |
        const result = input.step2_result;
        return { final_result: result };

2. Add Error Handling

agents:
  - name: process
    operation: code
    config:
      code: |
        try {
          const data = JSON.parse(input.json_string);
          return { success: true, data: data };
        } catch (error) {
          throw new Error(`Failed to parse JSON: ${error.message}`);
        }

3. Use Meaningful Variable Names

// Good: Clear variable names
const totalAmount = items.reduce((sum, item) => sum + item.price, 0);
const customerName = customer.first_name + ' ' + customer.last_name;

// Bad: Unclear names
const t = items.reduce((s, i) => s + i.p, 0);
const cn = c.f + ' ' + c.l;

4. Add Comments for Complex Logic

agents:
  - name: calculate-compound-interest
    operation: code
    config:
      code: |
        // Calculate compound interest using A = P(1 + r/n)^(nt)
        const principal = input.principal;
        const rate = input.rate / 100;  // Convert percentage to decimal
        const compounds = input.compounds_per_year;
        const years = input.years;

        const amount = principal * Math.pow(1 + rate / compounds, compounds * years);

        return {
          principal: principal,
          final_amount: Math.round(amount * 100) / 100,
          interest_earned: Math.round((amount - principal) * 100) / 100
        };

5. Validate Input Data

agents:
  - name: process-with-validation
    operation: code
    config:
      code: |
        // Validate input exists and has required fields
        if (!input.data) {
          throw new Error('Input data is required');
        }

        if (!Array.isArray(input.data)) {
          throw new Error('Input data must be an array');
        }

        if (input.data.length === 0) {
          throw new Error('Input data cannot be empty');
        }

        return { items_processed: input.data.length };

Integration Patterns

With Think Agents

ensemble: analyze-with-code-prep

flow:
  - agent: prepare
  - agent: analyze

agents:
  - name: prepare
    operation: code
    config:
      code: |
        // Prepare data for AI analysis
        return {
          summary: input.text.substring(0, 1000),
          word_count: input.text.split(' ').length,
          char_count: input.text.length
        };

  - name: analyze
    operation: think
    config:
      provider: anthropic
      model: claude-sonnet-4
      prompt: |
        Analyze this text and provide insights:
        ${input.prepare_output}

With Data Agents

ensemble: query-and-process

flow:
  - agent: fetch
  - agent: process

agents:
  - name: fetch
    operation: data
    config:
      database: postgres
      query: "query://get-users@v1"

  - name: process
    operation: code
    config:
      code: |
        // Process database results
        const users = input.fetch_output;
        return {
          total_users: users.length,
          active_users: users.filter(u => u.active).length,
          avg_age: users.reduce((sum, u) => sum + u.age, 0) / users.length
        };

outputs:
  stats: ${process.output}

JavaScript Runtime Environment

Code agents execute in a secure JavaScript runtime with:
  • Full ES6+ support - All modern JavaScript features
  • Built-in objects - Array, Object, Math, Date, JSON, etc.
  • Safe execution - Isolated from other agents
  • Input access - Access via input object
  • Output requirement - Must return a value

Available Globals

agents:
  - name: demo
    operation: code
    config:
      code: |
        // All standard JavaScript is available
        const now = new Date();
        const random = Math.random();
        const text = "Hello World";
        const arr = [1, 2, 3];
        const obj = { key: "value" };

        // Return result
        return {
          timestamp: now.toISOString(),
          random_number: random,
          arrays_work: arr.map(x => x * 2)
        };

Troubleshooting

Syntax Errors

Issue: Code doesn’t execute Solutions:
  1. Check JavaScript syntax (use node locally to test)
  2. Ensure all variables are defined
  3. Check for missing parentheses or braces

Runtime Errors

Issue: Code throws an error during execution Solutions:
  1. Add error handling with try/catch
  2. Validate input data before processing
  3. Check that referenced properties exist

Type Errors

Issue: Cannot perform operation on undefined value Solutions:
  1. Check input structure
  2. Add null/undefined checks
  3. Use optional chaining: obj?.property

Next Steps