Skip to main content

TypeScript API Reference

Build type-safe ensembles with full IDE support, autocomplete, and compile-time validation.

Why TypeScript?

TypeScript ensembles offer several advantages over YAML:
  • Type Safety: Catch configuration errors at compile time
  • IDE Support: Full autocomplete, inline documentation, and refactoring
  • Dynamic Logic: Build steps conditionally using code
  • Reusability: Share and compose steps across multiple ensembles
  • Testing: Easier unit testing of ensemble configurations

Quick Start

import { createEnsemble, step } from '@anthropic/conductor'

const myWorkflow = createEnsemble('my-workflow')
  .setDescription('A simple greeting workflow')
  .addStep(
    step('greeter')
      .agent('greeter')
      .input({ name: '${input.name}' })
  )
  .build()

export default myWorkflow

createEnsemble

Create a new ensemble with the fluent builder API.
import { createEnsemble } from '@anthropic/conductor'

const ensemble = createEnsemble('ensemble-name')

Methods

.setDescription(description: string)

Set a description for the ensemble.
createEnsemble('my-workflow')
  .setDescription('Processes customer orders and sends confirmations')

.setInput<T>()

Define the input type for the ensemble (TypeScript generics for type safety).
interface WorkflowInput {
  customerId: string
  orderItems: string[]
}

createEnsemble('order-processor')
  .setInput<WorkflowInput>()

.setOutput<T>()

Define the output type for the ensemble.
interface WorkflowOutput {
  orderId: string
  status: 'completed' | 'failed'
  total: number
}

createEnsemble('order-processor')
  .setOutput<WorkflowOutput>()

.addStep(step: Step)

Add a step to the ensemble. Steps execute sequentially by default, but steps without dependencies run in parallel.
createEnsemble('my-workflow')
  .addStep(step('fetch').agent('fetcher').input({ url: '${input.url}' }))
  .addStep(step('process').agent('processor').input({ data: '${fetch.output}' }))

.addSteps(...steps: Step[])

Add multiple steps at once.
createEnsemble('my-workflow')
  .addSteps(
    step('step1').agent('agent1'),
    step('step2').agent('agent2'),
    step('step3').agent('agent3')
  )

.build()

Finalize and return the ensemble configuration.
const ensemble = createEnsemble('my-workflow')
  .addStep(step('greeter').agent('greeter'))
  .build()

export default ensemble

step

Create an individual step within an ensemble.
import { step } from '@anthropic/conductor'

step('step-name')
  .agent('agent-name')
  .input({ key: 'value' })

Methods

.agent(name: string)

Reference an agent by name.
step('enrich')
  .agent('company-enricher')

.operation(type: string)

Use an inline operation instead of an agent.
step('analyze')
  .operation('think')
  .config({
    provider: 'openai',
    model: 'gpt-4o',
    prompt: 'Analyze: ${input.text}'
  })

.input(inputs: Record<string, any>)

Provide inputs to the step. Supports expression syntax like ${input.field} and ${previousStep.output}.
step('process')
  .agent('processor')
  .input({
    data: '${fetch.output}',
    userId: '${input.userId}',
    timestamp: '${Date.now()}'
  })

.config(config: Record<string, any>)

Provide configuration for an operation.
step('generate')
  .operation('think')
  .config({
    provider: 'anthropic',
    model: 'claude-3-5-sonnet-20241022',
    temperature: 0.7,
    maxTokens: 1000,
    prompt: '${input.prompt}'
  })

.condition(expression: string)

Conditionally execute the step.
step('send-premium-email')
  .agent('email-sender')
  .condition('${input.isPremiumUser === true}')

.retry(options: RetryOptions)

Configure retry behavior for the step.
step('api-call')
  .agent('fetcher')
  .retry({
    maxAttempts: 3,
    backoff: 'exponential',
    initialDelay: 1000,
    maxDelay: 30000
  })

.cache(options: CacheOptions)

Configure caching for the step.
step('expensive-analysis')
  .agent('analyzer')
  .cache({
    ttl: 3600,
    key: 'analysis-${input.documentId}'
  })

.timeout(ms: number)

Set a timeout for the step.
step('slow-operation')
  .agent('processor')
  .timeout(30000)

Flow Control Primitives

TypeScript ensembles support advanced flow control patterns through specialized primitives.

parallel

Execute multiple steps concurrently.
import { createEnsemble, step, parallel } from '@anthropic/conductor'

createEnsemble('multi-fetch')
  .addStep(
    parallel('fetch-all')
      .steps(
        step('fetch-a').agent('fetcher').input({ url: 'https://api-a.com' }),
        step('fetch-b').agent('fetcher').input({ url: 'https://api-b.com' }),
        step('fetch-c').agent('fetcher').input({ url: 'https://api-c.com' })
      )
  )
  .addStep(
    step('merge')
      .operation('code')
      .config({ script: 'scripts/merge-results' })
      .input({
        a: '${fetch-a.output}',
        b: '${fetch-b.output}',
        c: '${fetch-c.output}'
      })
  )

branch

Conditional branching based on a condition.
import { createEnsemble, step, branch } from '@anthropic/conductor'

createEnsemble('conditional-workflow')
  .addStep(
    step('classify')
      .operation('think')
      .config({ prompt: 'Classify priority: ${input.text}' })
  )
  .addStep(
    branch('route-by-priority')
      .condition('${classify.output === "urgent"}')
      .then(
        step('urgent-handler').agent('urgent-processor')
      )
      .else(
        step('normal-handler').agent('normal-processor')
      )
  )

foreach

Iterate over an array, executing a step for each item.
import { createEnsemble, step, foreach } from '@anthropic/conductor'

createEnsemble('batch-processor')
  .addStep(
    foreach('process-items')
      .items('${input.items}')
      .as('item')
      .step(
        step('process')
          .agent('item-processor')
          .input({ item: '${item}' })
      )
  )

Options

foreach('process-items')
  .items('${input.items}')
  .as('item')
  .index('idx')                    // Optional: variable name for index
  .concurrency(5)                  // Optional: max parallel executions
  .step(...)

tryStep

Error handling with try/catch semantics.
import { createEnsemble, step, tryStep } from '@anthropic/conductor'

createEnsemble('resilient-workflow')
  .addStep(
    tryStep('safe-fetch')
      .try(
        step('fetch').agent('fetcher').input({ url: '${input.url}' })
      )
      .catch(
        step('fallback').agent('cache-reader').input({ key: '${input.cacheKey}' })
      )
  )

switchStep

Multi-way branching based on a value.
import { createEnsemble, step, switchStep } from '@anthropic/conductor'

createEnsemble('router')
  .addStep(
    switchStep('route-by-type')
      .value('${input.type}')
      .case('email', step('email-handler').agent('email-processor'))
      .case('sms', step('sms-handler').agent('sms-processor'))
      .case('push', step('push-handler').agent('push-processor'))
      .default(step('default-handler').agent('generic-processor'))
  )

whileStep

Loop while a condition is true.
import { createEnsemble, step, whileStep } from '@anthropic/conductor'

createEnsemble('polling-workflow')
  .addStep(
    whileStep('poll-until-ready')
      .condition('${!status.ready}')
      .maxIterations(10)
      .step(
        step('check-status')
          .agent('status-checker')
          .input({ jobId: '${input.jobId}' })
      )
  )

mapReduce

Process items in parallel (map) then aggregate results (reduce).
import { createEnsemble, step, mapReduce } from '@anthropic/conductor'

createEnsemble('analyze-documents')
  .addStep(
    mapReduce('analyze-all')
      .items('${input.documents}')
      .map(
        step('analyze')
          .agent('document-analyzer')
          .input({ doc: '${item}' })
      )
      .reduce(
        step('aggregate')
          .agent('result-aggregator')
          .input({ results: '${mapResults}' })
      )
  )

Version Primitives (Edgit Integration)

Version primitives enable referencing components managed by Edgit with precise version pinning. This is essential for production workflows requiring reproducible deployments.

componentRef

Create a versioned reference to any component type.
import { componentRef } from '@anthropic/conductor'

// Exact version
const analyzer = componentRef('agent', 'analyzers/sentiment', '1.0.0')

// Compatible version constraint (semver ^)
const processor = componentRef('agent', 'processors/text', '^2.0.0', {
  fallback: '1.9.0',
  resolution: 'compatible'
})

// Latest version
const tool = componentRef('tool', 'search/web', 'latest')

Options

interface ComponentRefOptions {
  fallback?: string         // Fallback version if primary unavailable
  required?: boolean        // Fail if not found (default: true)
  resolution?: 'exact' | 'compatible' | 'latest-matching'
}

versionedAgent

Create a versioned agent reference for use in ensemble steps.
import { versionedAgent, createEnsemble } from '@anthropic/conductor'

// Simple versioned agent
const analyzer = versionedAgent('analyzers/sentiment', '1.0.0')

// With config override
const customAnalyzer = versionedAgent('analyzers/sentiment', '^1.0.0', {
  config: {
    model: 'claude-sonnet-4',
    temperature: 0.5
  },
  input: {
    text: '${input.content}'
  }
})

// Use in ensemble
const pipeline = createEnsemble({
  name: 'analysis-pipeline',
  steps: [
    versionedAgent('preprocessor', '2.1.0').toFlowStep(),
    versionedAgent('analyzer', '^1.0.0').toFlowStep(),
    versionedAgent('formatter', '1.0.0').toFlowStep()
  ]
})

Batch Creation

import { versionedAgents, createEnsemble } from '@anthropic/conductor'

// Define multiple versioned agents at once
const agents = versionedAgents({
  preprocessor: '2.1.0',
  analyzer: '^1.0.0',
  formatter: '~1.2.0',
  // With options
  scorer: {
    version: '^3.0.0',
    options: {
      config: { threshold: 0.8 }
    }
  }
})

// Use in ensemble
const pipeline = createEnsemble({
  name: 'scoring-pipeline',
  steps: [
    agents.preprocessor.toFlowStep(),
    agents.analyzer.toFlowStep(),
    agents.scorer.toFlowStep()
  ]
})

versionedEnsemble

Reference and compose versioned sub-ensembles.
import { versionedEnsemble, createEnsemble, step } from '@anthropic/conductor'

// Reference a versioned sub-ensemble
const analysisPipeline = versionedEnsemble('pipelines/analysis', '^2.0.0', {
  input: { data: '${preprocess.output}' },
  inheritState: true
})

// Compose in a parent ensemble
const mainPipeline = createEnsemble({
  name: 'main-pipeline',
  steps: [
    step('preprocess'),
    // Invoke the versioned sub-ensemble
    { ...analysisPipeline.toInvocation() },
    step('postprocess')
  ]
})

deploymentRef

Reference components by deployment environment rather than explicit version.
import { deploymentRef, componentRef, versionedAgent } from '@anthropic/conductor'

// Reference production deployment
const prodAnalyzer = deploymentRef(
  componentRef('agent', 'analyzers/sentiment', 'latest'),
  'production'
)

// Reference staging with production fallback
const stagingPipeline = deploymentRef(
  versionedAgent('analyzers/sentiment', 'latest'),
  'staging',
  { fallback: 'production' }
)

// Dynamic environment
const dynamicRef = deploymentRef(
  versionedAgent('processor', 'latest'),
  process.env.DEPLOYMENT_ENV || 'development'
)

Version Constraints

Support for semver-style version constraints:
ConstraintMeaningExample
1.2.3Exact versionOnly v1.2.3
^1.2.0Compatible (same major)v1.2.0, v1.3.0, v1.9.9
~1.2.0Patch-compatible (same minor)v1.2.0, v1.2.1, v1.2.9
>=1.0.0Minimum versionv1.0.0 and above
<=2.0.0Maximum versionv2.0.0 and below
latestLatest availableMost recent version
stableLatest stableMost recent non-prerelease

Utility Functions

import { parseVersion, satisfiesVersion } from '@anthropic/conductor'

// Parse version strings
parseVersion('1.2.3')      // { major: 1, minor: 2, patch: 3 }
parseVersion('^1.2.0')     // { constraint: '^', major: 1, minor: 2, patch: 0 }
parseVersion('latest')     // { tag: 'latest' }

// Check version compatibility
satisfiesVersion('1.2.3', '^1.0.0')   // true
satisfiesVersion('2.0.0', '^1.0.0')   // false
satisfiesVersion('1.2.5', '~1.2.0')   // true

Integration with Edgit

Version primitives integrate directly with Edgit’s Git tag namespaces:
const analyzer = versionedAgent('sentiment-analyzer', '1.0.0')

// Convert to Edgit Git tag format
console.log(analyzer.toGitTag())
// Output: agents/sentiment-analyzer/v1.0.0

const pipeline = versionedEnsemble('data-pipeline', '2.0.0')
console.log(pipeline.toGitTag())
// Output: ensembles/data-pipeline/v2.0.0

Type-Specific Namespaces

Both Edgit (Git tags) and Conductor (KV storage) use type-specific namespaces:
Component TypeGit Tag NamespaceKV Key Prefix
promptprompts/prompts:
schemaschemas/schemas:
configconfigs/configs:
scriptscripts/scripts:
queryqueries/queries:
templatetemplates/templates:
tooltools/tools:
agentagents/agents:
ensembleensembles/ensembles:
This alignment ensures Git tags created by Edgit map directly to KV keys used by Conductor at runtime.

Complete Example

Here’s a complete TypeScript ensemble that demonstrates multiple features:
// ensembles/customer-intelligence.ts
import {
  createEnsemble,
  step,
  parallel,
  branch,
  tryStep
} from '@anthropic/conductor'

interface CustomerInput {
  customerId: string
  includeHistory: boolean
}

interface CustomerOutput {
  profile: Record<string, unknown>
  sentiment: string
  recommendations: string[]
}

const customerIntelligence = createEnsemble('customer-intelligence')
  .setDescription('Gather and analyze customer data from multiple sources')
  .setInput<CustomerInput>()
  .setOutput<CustomerOutput>()

  // Parallel data fetching
  .addStep(
    parallel('fetch-data')
      .steps(
        step('fetch-profile')
          .agent('crm-connector')
          .input({ customerId: '${input.customerId}' }),
        step('fetch-transactions')
          .agent('transaction-fetcher')
          .input({ customerId: '${input.customerId}' }),
        step('fetch-support-tickets')
          .agent('support-connector')
          .input({ customerId: '${input.customerId}' })
      )
  )

  // Conditional history fetch
  .addStep(
    branch('check-history')
      .condition('${input.includeHistory}')
      .then(
        step('fetch-history')
          .agent('history-fetcher')
          .input({ customerId: '${input.customerId}' })
      )
  )

  // AI analysis with fallback
  .addStep(
    tryStep('analyze')
      .try(
        step('ai-analysis')
          .operation('think')
          .config({
            provider: 'anthropic',
            model: 'claude-3-5-sonnet-20241022',
            prompt: `
              Analyze this customer:
              Profile: \${fetch-profile.output}
              Transactions: \${fetch-transactions.output}
              Support: \${fetch-support-tickets.output}

              Provide: sentiment, key insights, recommendations
            `
          })
      )
      .catch(
        step('basic-analysis')
          .operation('think')
          .config({
            provider: 'openai',
            model: 'gpt-4o-mini',
            prompt: 'Provide basic analysis for customer: ${fetch-profile.output}'
          })
      )
  )

  .build()

export default customerIntelligence

Expression Syntax

TypeScript ensembles support the same expression syntax as YAML:

Variable Access

'${input.fieldName}'              // Input variable
'${env.API_KEY}'                  // Environment variable
'${state.counter}'                // State variable
'${stepName.output}'              // Step output
'${stepName.output.nested.field}' // Nested output

Array Indexing

'${input.items[0]}'               // First array element
'${input.users[0].name}'          // Property of first element
'${data.results[1].meta.id}'      // Deep nested access

Nullish Coalescing (??)

Returns the first value that is not null or undefined:
'${input.name ?? "default"}'      // Use "default" if null/undefined
'${input.query.name ?? input.body.name ?? "Guest"}'  // Chain fallbacks
'${input.count ?? 0}'             // Preserves 0 (unlike ||)

Falsy Coalescing (||)

Returns the first truthy value (catches "", 0, false, null, undefined):
'${input.name || "default"}'      // Use "default" if empty/falsy
'${input.count || 10}'            // Use 10 if count is 0
'${input.a || input.b || "fallback"}'  // Chain values

Ternary Conditionals (?:)

'${input.enabled ? "yes" : "no"}'       // Returns "yes" if truthy
'${input.premium ? 100 : 10}'           // Numeric conditional
'${input.type ? input.type : "default"}' // Path in branches

Boolean Negation (!)

'${!input.disabled}'              // true if disabled is falsy
'${!stepName.executed}'           // true if step didn't run

Conditionals

'${input.value > 10}'
'${stepName.success}'
'${stepName.failed}'
'${!stepName.executed}'
'${input.type === "premium"}'
'${input.age >= 18 && input.verified}'

Filter Chains

'${input.text | uppercase}'       // Apply uppercase filter
'${input.text | split(" ") | first}'  // Chain filters
'${input.items | length}'         // Array/string length

Functions

'${Date.now()}'
'${JSON.stringify(object)}'
'${array.length}'
'${array.map(item => item.id)}'
'${string.toUpperCase()}'

AgentExecutionContext

When writing TypeScript handlers for agents, you receive a rich execution context with access to all project resources.
interface AgentExecutionContext {
  input: Record<string, unknown>  // Input from ensemble
  env: Env                        // Cloudflare Workers env
  request?: Request               // Original HTTP request (if triggered by HTTP)

  // Component registries
  schemas: {
    get(name: string): Schema | undefined
    validate(name: string, data: unknown): ValidationResult
    isValid(name: string, data: unknown): boolean
  }

  prompts: {
    get(name: string): Prompt | undefined
    render(name: string, vars: Record<string, unknown>): string
  }

  configs: {
    get(name: string): unknown
  }

  queries: {
    getSql(name: string): string
  }

  scripts: {
    get(name: string): Function
  }

  templates: {
    render(name: string, vars: Record<string, unknown>): string
  }

  // Discovery registries
  agentRegistry: {
    list(): AgentMetadata[]
    get(name: string): AgentDefinition | undefined
  }

  ensembleRegistry: {
    list(): EnsembleMetadata[]
    get(name: string): EnsembleDefinition | undefined
  }

  // Project config
  config: ConductorConfig
}

Using AgentExecutionContext

// agents/my-agent/handler.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'

export default async function handler(ctx: AgentExecutionContext) {
  // Access input from ensemble
  const { userId, action } = ctx.input

  // Validate with shared schema
  const isValid = ctx.schemas.isValid('user-request', ctx.input)
  if (!isValid) {
    throw new Error('Invalid input')
  }

  // Render prompt with variables
  const prompt = ctx.prompts.render('analyze-user', {
    userId,
    context: 'detailed analysis'
  })

  // Get shared configuration
  const apiConfig = ctx.configs.get('api-settings')

  // Access SQL query template
  const query = ctx.queries.getSql('user-lookup')

  // Access environment variables
  const apiKey = ctx.env.API_KEY

  // Access project config
  const projectName = ctx.config.name

  // Discover available agents
  const agents = ctx.agentRegistry.list()

  return {
    result: 'success',
    data: { /* ... */ }
  }
}

Component Registries

Component registries provide access to versioned, reusable project resources:
// Schemas - validation and type checking
const schema = ctx.schemas.get('user-profile')
const validation = ctx.schemas.validate('order', orderData)
if (!validation.valid) {
  console.error(validation.errors)
}

// Prompts - templated AI instructions
const prompt = ctx.prompts.get('sentiment-analyzer')
const rendered = ctx.prompts.render('sentiment-analyzer', {
  text: userInput,
  language: 'en'
})

// Configs - shared configuration
const settings = ctx.configs.get('app-settings')

// Queries - SQL templates
const sqlQuery = ctx.queries.getSql('analytics-report')

// Scripts - shared JavaScript/TypeScript modules
const utilFunction = ctx.scripts.get('data-transformer')

// Templates - HTML/text templates
const html = ctx.templates.render('email-template', {
  userName: 'Alice',
  message: 'Welcome!'
})

Discovery Registries

Discovery registries enable runtime introspection of available agents and ensembles:
// List all available agents
const allAgents = ctx.agentRegistry.list()
console.log(allAgents.map(a => a.name))

// Get specific agent definition
const enricher = ctx.agentRegistry.get('company-enricher')
if (enricher) {
  console.log(enricher.description)
  console.log(enricher.inputs)
}

// List all ensembles
const ensembles = ctx.ensembleRegistry.list()

// Get specific ensemble
const workflow = ctx.ensembleRegistry.get('user-onboarding')

Project Configuration Access

Access the full project configuration from any agent:
// Project metadata
const projectName = ctx.config.name
const version = ctx.config.version

// Documentation settings
const docsUI = ctx.config.docs?.ui
const docsCache = ctx.config.docs?.cache

// Observability settings
const loggingLevel = ctx.config.observability?.logging

Type Definitions

EnsembleDefinition

interface EnsembleDefinition {
  name: string
  description?: string
  apiExecutable?: boolean        // Control Execute API access (default: true)
  trigger?: TriggerConfig[]
  inputs?: Record<string, InputDefinition>
  state?: StateConfig
  agents: AgentStep[]
  output?: OutputConfig
}

AgentDefinition

interface AgentDefinition {
  name: string
  description?: string
  apiExecutable?: boolean        // Control Execute API access (default: true)
  inputs?: Record<string, InputDefinition>
  operations: OperationConfig[]
  outputs?: Record<string, string>
}

OutputConfig

interface OutputConfig {
  status?: number
  headers?: Record<string, string>
  format?: OutputFormat | FormatType
  body?: Record<string, unknown>
}

OutputFormat

type FormatType =
  | 'json'      // application/json
  | 'text'      // text/plain
  | 'html'      // text/html
  | 'xml'       // application/xml
  | 'csv'       // text/csv
  | 'markdown'  // text/markdown
  | 'yaml'      // application/x-yaml
  | 'ics'       // text/calendar
  | 'rss'       // application/rss+xml
  | 'atom'      // application/atom+xml

interface OutputFormat {
  type: FormatType
  extract?: string               // Field to extract from body for serialization
}

ApiConfig

Project-level configuration for Execute API access control.
interface ApiConfig {
  execution?: {
    agents?: {
      requireExplicit?: boolean  // When true, agents must have apiExecutable: true
    }
    ensembles?: {
      requireExplicit?: boolean  // When true, ensembles must have apiExecutable: true
    }
  }
}

RetryOptions

interface RetryOptions {
  maxAttempts?: number           // Default: 3
  backoff?: 'exponential' | 'linear'  // Default: 'exponential'
  initialDelay?: number          // Default: 1000 (ms)
  maxDelay?: number              // Default: 30000 (ms)
  retryOn?: number[]             // HTTP status codes to retry
}

CacheOptions

interface CacheOptions {
  ttl: number                    // Time to live in seconds
  key: string                    // Cache key (supports expressions)
}

Step

interface Step {
  name: string
  agent?: string
  operation?: string
  input?: Record<string, unknown>
  config?: Record<string, unknown>
  condition?: string
  retry?: RetryOptions
  cache?: CacheOptions
  timeout?: number
}

Migration from YAML

Converting a YAML ensemble to TypeScript:
  • YAML
  • TypeScript
ensemble: greet-user

agents:
  - name: greeter
    agent: greeter
    inputs:
      name: ${input.name}
      style: ${input.style}

  - name: formatter
    operation: code
    config:
      script: scripts/format-greeting
    input:
      greeting: ${greeter.output}

output:
  message: ${formatter.output}

Best Practices

  1. Export as default - Always export your ensemble as the default export
  2. Use TypeScript generics - Define input/output types for better type safety
  3. Organize by domain - Keep related ensembles in the same directory
  4. Reuse steps - Extract common patterns into shared step factories
  5. Validate early - Use ensemble conductor validate to check TypeScript ensembles

Next Steps