Skip to main content

Overview

Auto-discovery is Conductor’s zero-config approach to loading agents and ensembles. Instead of manually importing and registering each agent, Conductor automatically discovers them from your project directory at build time.

Before Auto-Discovery

// ❌ Manual registration (old approach)
import greetConfig from './agents/greet/agent.yaml'
import greetHandler from './agents/greet/index.ts'
import analyzeConfig from './agents/analyze/agent.yaml'
import analyzeHandler from './agents/analyze/index.ts'
// ... import 50 more agents ...

const loader = new AgentLoader({ env, ctx })
loader.registerAgent(greetConfig, greetHandler)
loader.registerAgent(analyzeConfig, analyzeHandler)
// ... register 50 more agents ...

After Auto-Discovery

// ✅ Auto-discovery (new approach)
import { createAutoDiscoveryAPI } from '@ensemble-edge/conductor/api'
import { agents } from 'virtual:conductor-agents'
import { ensembles } from 'virtual:conductor-ensembles'

export default createAutoDiscoveryAPI({
  agents,
  ensembles,
  autoDiscover: true,
})
That’s it! All agents and ensembles are automatically discovered and registered.

How It Works

Auto-discovery uses Vite plugins that scan your project directory at build time and generate virtual modules containing all your agents and ensembles.
1
Step 1: Vite Plugin Scans Project
2
During the build process, Vite plugins scan your project:
3
  • vite-plugin-agent-discovery → Scans agents/**/*.yaml
  • vite-plugin-ensemble-discovery → Scans ensembles/**/*.yaml
  • 4
    Step 2: Virtual Modules Generated
    5
    The plugins generate virtual modules that bundle:
    6
    // virtual:conductor-agents (generated at build time)
    export const agents = [
      {
        name: 'greet',
        config: '...yaml content...',
        handler: () => import('./agents/greet/index.ts'),
      },
      {
        name: 'analyze',
        config: '...yaml content...',
        handler: () => import('./agents/analyze/index.ts'),
      },
      // ... all discovered agents
    ]
    
    7
    Step 3: Runtime Auto-Discovery
    8
    At runtime, AgentLoader.autoDiscover() processes the virtual modules:
    9
  • Parses YAML configs
  • Loads handler functions (if present)
  • Creates agent instances
  • Registers them for execution

  • Quick Start

    Installation

    Auto-discovery is built into Conductor templates. If you’re starting a new project:
    npx @ensemble-edge/edgit init my-project
    cd my-project
    
    The template includes auto-discovery by default.

    Manual Setup

    If you have an existing project, add these plugins to vite.config.ts:
    import { agentDiscoveryPlugin } from './scripts/vite-plugin-agent-discovery'
    import { ensembleDiscoveryPlugin } from './scripts/vite-plugin-ensemble-discovery'
    
    export default defineConfig({
      plugins: [
        agentDiscoveryPlugin(), // Discovers agents/**/*.yaml
        ensembleDiscoveryPlugin(), // Discovers ensembles/**/*.yaml
      ],
    })
    

    Using Auto-Discovery

    Create your entry point (src/index.ts):
    import { createAutoDiscoveryAPI } from '@ensemble-edge/conductor/api'
    import { ExecutionState, HITLState } from '@ensemble-edge/conductor/cloudflare'
    import { agents } from 'virtual:conductor-agents'
    import { ensembles } from 'virtual:conductor-ensembles'
    
    export default createAutoDiscoveryAPI({
      // Virtual modules from build-time discovery
      agents,
      ensembles,
    
      // Enable auto-discovery (default: true)
      autoDiscover: true,
    
      // Authentication
      auth: {
        allowAnonymous: true, // Disable in production
      },
    
      // Logging
      logging: true,
    })
    
    // Export Durable Objects
    export { ExecutionState, HITLState }
    

    File Structure

    Auto-discovery expects this structure:
    my-project/
    ├── agents/               # Auto-discovered
    │   ├── greet/
    │   │   ├── agent.yaml    # ✅ Discovered
    │   │   └── index.ts      # ✅ Handler (optional)
    │   ├── analyze/
    │   │   └── agent.yaml    # ✅ Discovered (no handler)
    │   └── generate-docs/    # ❌ Excluded by default
    │       └── docs.yaml
    ├── ensembles/            # Auto-discovered
    │   ├── blog-workflow.yaml  # ✅ Discovered
    │   └── user-onboarding.yaml # ✅ Discovered
    ├── pages/                # Auto-discovered (separate plugin)
    ├── docs/                 # Auto-discovered (separate plugin)
    └── src/
        └── index.ts          # Entry point
    

    What Gets Discovered

    Pattern: agents/**/*.yamlIncluded: agents/examples/** (by default, set includeExamples: false to exclude)Excluded: agents/generate-docs/** (by default)Handler Detection: If agents/{name}/index.ts exists, it’s auto-loaded as the handlerExample:
    agents/
    ├── greet/
    │   ├── agent.yaml      # Config
    │   └── index.ts        # Handler (optional)
    ├── analyze/
    │   └── agent.yaml      # Config only
    └── examples/           # ✅ Included by default
        └── hello/
            └── agent.yaml
    
    Pattern: ensembles/**/*.yamlNo exclusionsExample:
    ensembles/
    ├── blog-workflow.yaml
    └── workflows/
        └── user-onboarding.yaml
    

    API Reference

    createAutoDiscoveryAPI(config)

    Creates a Conductor API with auto-discovery.
    config
    AutoDiscoveryAPIConfig
    Configuration options
    config.autoDiscover
    boolean
    default:true
    Enable auto-discovery of agents and ensembles
    config.agents
    AgentDefinition[]
    Virtual agents module from virtual:conductor-agents
    config.ensembles
    EnsembleDefinition[]
    Virtual ensembles module from virtual:conductor-ensembles
    config.auth
    AuthConfig
    Authentication configuration
    config.logging
    boolean
    default:true
    Enable request logging
    config.cors
    CORSConfig
    CORS configuration
    Returns: ExportedHandler<Env>

    getAgentLoader()

    Get the initialized AgentLoader instance.
    import { getAgentLoader } from '@ensemble-edge/conductor/api'
    
    const loader = getAgentLoader()
    if (loader) {
      console.log(`Loaded agents: ${loader.getAgentNames().join(', ')}`)
    }
    
    Returns: AgentLoader | null

    getEnsembleLoader()

    Get the initialized EnsembleLoader instance.
    import { getEnsembleLoader } from '@ensemble-edge/conductor/api'
    
    const loader = getEnsembleLoader()
    if (loader) {
      console.log(`Loaded ensembles: ${loader.getEnsembleNames().join(', ')}`)
    }
    
    Returns: EnsembleLoader | null

    Advanced Usage

    Including/Excluding Examples

    By default, agents in agents/examples/ are included in discovery. To exclude example agents:
    // vite.config.ts
    export default defineConfig({
      plugins: [
        agentDiscoveryPlugin({
          includeExamples: false, // Exclude agents/examples/
        }),
      ],
    })
    

    Custom Exclusions

    Exclude specific directories from agent discovery:
    // vite.config.ts
    export default defineConfig({
      plugins: [
        agentDiscoveryPlugin({
          agentsDir: 'agents',
          excludeDirs: ['generate-docs', 'internal', 'experimental'],
        }),
      ],
    })
    

    Custom File Extensions

    Support .yml instead of .yaml:
    export default defineConfig({
      plugins: [
        agentDiscoveryPlugin({ fileExtension: '.yml' }),
        ensembleDiscoveryPlugin({ fileExtension: '.yml' }),
      ],
    })
    

    Manual Loader Access

    Use the loaders directly for advanced use cases:
    import { getAgentLoader, getEnsembleLoader } from '@ensemble-edge/conductor/api'
    
    export default {
      async fetch(request: Request, env: Env, ctx: ExecutionContext) {
        const agentLoader = getAgentLoader()
        const ensembleLoader = getEnsembleLoader()
    
        if (!agentLoader || !ensembleLoader) {
          return new Response('Loaders not initialized', { status: 503 })
        }
    
        // Custom logic using loaders
        const agents = agentLoader.getAllAgents()
        const ensembles = ensembleLoader.getAllEnsembles()
    
        return Response.json({
          agents: agents.length,
          ensembles: ensembles.length,
        })
      },
    }
    

    Troubleshooting

    Agents Not Found

    Problem: Auto-discovery doesn’t find your agents Solution:
    1. Check file structure matches agents/**/*.yaml
    2. Ensure agents aren’t in excluded directories
    3. Check Vite plugin is registered in vite.config.ts
    4. Rebuild: pnpm run build

    TypeScript Errors

    Problem: Cannot find module 'virtual:conductor-agents' Solution: Add type declarations:
    // src/virtual-modules.d.ts
    declare module 'virtual:conductor-agents' {
      export interface AgentDefinition {
        name: string
        config: string
        handler?: () => Promise<any>
      }
    
      export const agents: AgentDefinition[]
      export const agentsMap: Map<string, AgentDefinition>
    }
    
    declare module 'virtual:conductor-ensembles' {
      export interface EnsembleDefinition {
        name: string
        config: string
      }
    
      export const ensembles: EnsembleDefinition[]
      export const ensemblesMap: Map<string, EnsembleDefinition>
    }
    

    Build Errors

    Problem: Build fails with “Directory not found” Solution: Create empty directories if needed:
    mkdir -p agents ensembles
    
    The Vite plugins handle missing directories gracefully but some bundlers may require them to exist.

    Migration Guide

    From Manual Registration

    1
    Step 1: Remove Manual Imports
    2
    Delete all manual agent/ensemble imports from your entry point.
    3
    Step 2: Add Virtual Module Imports
    4
    import { agents } from 'virtual:conductor-agents'
    import { ensembles } from 'virtual:conductor-ensembles'
    
    5
    Step 3: Replace with createAutoDiscoveryAPI
    6
    export default createAutoDiscoveryAPI({
      agents,
      ensembles,
      autoDiscover: true,
      auth: { allowAnonymous: true },
    })
    
    7
    Step 4: Rebuild and Test
    8
    pnpm run build
    npx wrangler dev
    

    Discovery Registries (ctx API)

    In addition to auto-discovering agents and ensembles at build time, Conductor provides runtime access to discovery registries through the ctx API. This allows agents to introspect what’s available in the project.

    ctx.agentRegistry

    Access the agent registry to list and inspect available agents:

    ctx.agentRegistry.list()

    List all available agents in the project:
    // agents/inspector/index.ts
    import type { AgentExecutionContext } from '@ensemble-edge/conductor'
    
    export default async function listAgents(ctx: AgentExecutionContext) {
      // List all available agents
      const agents = await ctx.agentRegistry.list()
    
      return {
        total: agents.length,
        agents: agents.map(a => ({
          name: a.name,
          description: a.description
        }))
      }
    }
    

    ctx.agentRegistry.get(name)

    Get a specific agent definition:
    // agents/inspector/index.ts
    import type { AgentExecutionContext } from '@ensemble-edge/conductor'
    
    export default async function inspectAgent(ctx: AgentExecutionContext) {
      const { agentName } = ctx.input as { agentName: string }
    
      // Get agent definition
      const agent = await ctx.agentRegistry.get(agentName)
    
      return {
        name: agent.name,
        description: agent.description,
        operation: agent.operation,
        schema: agent.schema
      }
    }
    

    ctx.ensembleRegistry

    Access the ensemble registry to list and inspect available ensembles:

    ctx.ensembleRegistry.list()

    List all available ensembles in the project:
    // agents/ensemble-lister/index.ts
    import type { AgentExecutionContext } from '@ensemble-edge/conductor'
    
    export default async function listEnsembles(ctx: AgentExecutionContext) {
      // List all available ensembles
      const ensembles = await ctx.ensembleRegistry.list()
    
      return {
        total: ensembles.length,
        ensembles: ensembles.map(e => ({
          name: e.name,
          description: e.description,
          triggers: e.trigger?.map(t => t.type)
        }))
      }
    }
    

    ctx.ensembleRegistry.get(name)

    Get a specific ensemble definition:
    // agents/ensemble-inspector/index.ts
    import type { AgentExecutionContext } from '@ensemble-edge/conductor'
    
    export default async function inspectEnsemble(ctx: AgentExecutionContext) {
      const { ensembleName } = ctx.input as { ensembleName: string }
    
      // Get ensemble definition
      const ensemble = await ctx.ensembleRegistry.get(ensembleName)
    
      return {
        name: ensemble.name,
        description: ensemble.description,
        triggers: ensemble.trigger,
        flow: ensemble.flow?.map(step => ({
          name: step.name,
          agent: step.agent,
          operation: step.operation
        }))
      }
    }
    

    ctx.config

    Access the project’s Conductor configuration:
    // agents/config-inspector/index.ts
    import type { AgentExecutionContext } from '@ensemble-edge/conductor'
    
    export default async function inspectConfig(ctx: AgentExecutionContext) {
      // Access project configuration
      const config = ctx.config
    
      return {
        version: config.version,
        name: config.name,
        components: {
          schemas: Object.keys(config.components?.schemas || {}),
          prompts: Object.keys(config.components?.prompts || {}),
          configs: Object.keys(config.components?.configs || {})
        }
      }
    }
    

    Complete Example: Dynamic Routing Agent

    // agents/dynamic-router/index.ts
    import type { AgentExecutionContext } from '@ensemble-edge/conductor'
    
    export default async function routeRequest(ctx: AgentExecutionContext) {
      const { action } = ctx.input as { action: string }
    
      // Check if agent exists for this action
      const agents = await ctx.agentRegistry.list()
      const targetAgent = agents.find(a => a.name === `handle-${action}`)
    
      if (!targetAgent) {
        return {
          success: false,
          error: `No agent found for action: ${action}`,
          availableAgents: agents.map(a => a.name)
        }
      }
    
      return {
        success: true,
        agent: targetAgent.name,
        description: targetAgent.description
      }
    }
    

    Complete Example: Documentation Generator

    // agents/docs-generator/index.ts
    import type { AgentExecutionContext } from '@ensemble-edge/conductor'
    
    export default async function generateDocs(ctx: AgentExecutionContext) {
      // List all agents and ensembles
      const agents = await ctx.agentRegistry.list()
      const ensembles = await ctx.ensembleRegistry.list()
    
      // Generate documentation
      const docs = {
        project: ctx.config.name,
        version: ctx.config.version,
        agents: agents.map(a => ({
          name: a.name,
          description: a.description,
          operation: a.operation,
          inputs: a.schema?.input,
          outputs: a.schema?.output
        })),
        ensembles: ensembles.map(e => ({
          name: e.name,
          description: e.description,
          triggers: e.trigger?.map(t => t.type),
          steps: e.flow?.length || 0
        }))
      }
    
      return docs
    }
    

    Use Cases

    Discovery registries enable powerful introspection patterns:
    1. Dynamic Routing: Route requests to agents based on runtime conditions
    2. Documentation Generation: Auto-generate API docs from agent definitions
    3. Validation: Check if required agents exist before execution
    4. Monitoring: List all available capabilities for observability
    5. Development Tools: Build CLI tools that inspect project structure

    Benefits

    Zero Config

    No manual imports or registration required

    Fast Development

    Add new agents by creating files—no code changes

    Type Safe

    Full TypeScript support with virtual module types

    Production Ready

    Build-time discovery ensures zero runtime overhead

    Next Steps

    Your First Agent

    Create your first auto-discovered agent

    Your First Ensemble

    Create your first auto-discovered ensemble

    Deployment

    Deploy your auto-discovery project to production