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.
Step 1: Vite Plugin Scans Project
During the build process, Vite plugins scan your project:
vite-plugin-agent-discovery → Scans agents/**/*.yaml
vite-plugin-ensemble-discovery → Scans ensembles/**/*.yaml
Step 2: Virtual Modules Generated
The plugins generate virtual modules that bundle:
// 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
]
Step 3: Runtime Auto-Discovery
At runtime, AgentLoader.autoDiscover() processes the virtual modules:
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 exclusions Example: ensembles/
├── blog-workflow.yaml
└── workflows/
└── user-onboarding.yaml
API Reference
createAutoDiscoveryAPI(config)
Creates a Conductor API with auto-discovery.
Enable auto-discovery of agents and ensembles
Virtual agents module from virtual:conductor-agents
Virtual ensembles module from virtual:conductor-ensembles
Authentication 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:
Check file structure matches agents/**/*.yaml
Ensure agents aren’t in excluded directories
Check Vite plugin is registered in vite.config.ts
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
Step 1: Remove Manual Imports
Delete all manual agent/ensemble imports from your entry point.
Step 2: Add Virtual Module Imports
import { agents } from 'virtual:conductor-agents'
import { ensembles } from 'virtual:conductor-ensembles'
Step 3: Replace with createAutoDiscoveryAPI
export default createAutoDiscoveryAPI ({
agents ,
ensembles ,
autoDiscover: true ,
auth: { allowAnonymous: true } ,
})
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:
Dynamic Routing : Route requests to agents based on runtime conditions
Documentation Generation : Auto-generate API docs from agent definitions
Validation : Check if required agents exist before execution
Monitoring : List all available capabilities for observability
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