{"id":32669033,"url":"https://github.com/ensemble-edge/conductor","last_synced_at":"2026-05-06T00:06:54.865Z","repository":{"id":321622642,"uuid":"1086486771","full_name":"ensemble-edge/conductor","owner":"ensemble-edge","description":"Edge-native orchestration runtime for AI components. Executes ensembles on Cloudflare's global network with sub-50ms latency.","archived":false,"fork":false,"pushed_at":"2026-03-28T16:21:18.000Z","size":14508,"stargazers_count":1,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-28T18:26:28.064Z","etag":null,"topics":["agents","ai","cloudflare","edge","orchestration","typescript","workers"],"latest_commit_sha":null,"homepage":"https://docs.ensemble.ai/conductor/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ensemble-edge.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":"COPYRIGHT","agents":null,"dco":null,"cla":null}},"created_at":"2025-10-30T13:39:53.000Z","updated_at":"2026-03-28T16:20:03.000Z","dependencies_parsed_at":"2025-10-30T17:34:13.310Z","dependency_job_id":null,"html_url":"https://github.com/ensemble-edge/conductor","commit_stats":null,"previous_names":["ensemble-edge/conductor"],"tags_count":101,"template":false,"template_full_name":null,"purl":"pkg:github/ensemble-edge/conductor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ensemble-edge%2Fconductor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ensemble-edge%2Fconductor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ensemble-edge%2Fconductor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ensemble-edge%2Fconductor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ensemble-edge","download_url":"https://codeload.github.com/ensemble-edge/conductor/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ensemble-edge%2Fconductor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32672688,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-05T11:29:49.557Z","status":"ssl_error","status_checked_at":"2026-05-05T11:29:48.587Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai","cloudflare","edge","orchestration","typescript","workers"],"created_at":"2025-11-01T02:01:56.417Z","updated_at":"2026-05-06T00:06:54.853Z","avatar_url":"https://github.com/ensemble-edge.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @ensemble-edge/conductor\n\n\u003e Edge-native AI orchestration — built exclusively for Cloudflare Workers, not ported from Lambda.\n\n[![npm version](https://img.shields.io/npm/v/@ensemble-edge/conductor.svg)](https://www.npmjs.com/package/@ensemble-edge/conductor)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)\n\n## Overview\n\n**Conductor** is an orchestration runtime that executes YAML-defined workflows at the edge using Cloudflare Workers. Purpose-built for V8 isolates—zero cold starts, 300+ global locations, native access to Workers AI, KV, D1, R2, Durable Objects, and Vectorize.\n\nThink of it as the runtime engine for your AI workflows—agents are musicians, ensembles are sheet music, and Conductor performs the symphony.\n\n### Key Features\n\n- 🚀 **Edge-Native** - Runs on Cloudflare Workers for sub-50ms latency globally\n- 📝 **YAML-Driven** - Define workflows as simple, readable YAML files\n- 🎯 **Type-Safe** - Full TypeScript support with strong typing\n- 🧪 **Built-in Testing** - 1500+ tests passing, comprehensive mocks, custom matchers\n- 🔄 **State Management** - Built-in state sharing across agent executions\n- 💾 **Integrated Caching** - KV-based caching for performance and cost optimization\n- 🧩 **Composable Operations** - 13 operation types: think, code, storage, data, http, tools, email, sms, html, pdf, form, queue, docs\n- 🛠️ **CLI Tools** - Project scaffolding, agent generation, and upgrades\n- 📦 **SDK** - Client library, testing utilities, and agent factories\n- 🔁 **Durable Objects** - Stateful workflows with strong consistency (ExecutionState, HITL)\n- ⏰ **Scheduled Execution** - Cron-based ensemble triggers for automated workflows\n- 🪝 **Webhooks** - HTTP triggers for ensemble execution\n- 🤝 **Human-in-the-Loop** - Approval workflows with resumption support\n- 📊 **Async Execution Tracking** - Real-time status tracking for long-running workflows\n- 🎯 **Scoring System** - Quality evaluation with automatic retry logic\n- 🏗️ **Build Triggers** - Static generation at build time\n- 🖥️ **CLI Triggers** - Custom developer commands\n- 🔀 **Multi-path HTTP** - Multiple routes per ensemble\n\n## Getting Started\n\n### Quick Start\n\n```bash\n# Create a new project (no installation needed)\nnpx @ensemble-edge/ensemble conductor init my-project\ncd my-project\n\n# Install and start dev server\npnpm install\nensemble conductor start\n```\n\n\u003e **Tip**: For CI/CD pipelines, use the `-y` flag to skip all interactive prompts:\n\u003e ```bash\n\u003e npx @ensemble-edge/conductor init my-project -y\n\u003e ```\n\nThe init command creates:\n- ✅ Complete project structure\n- ✅ 10 example pages (static, dynamic, forms, SSR)\n- ✅ Working hello-world ensemble\n- ✅ Example agents and tests\n- ✅ Ready to run immediately\n\n### Your Project Structure\n\nAfter running `conductor init`, here's what you'll have:\n\n```\nmy-project/\n├── src/\n│   ├── index.ts                 # 🔧 Worker entry point (Choose: Built-in API or custom)\n│   └── lib/                     # 👈 YOUR UTILITIES - Shared helpers, utilities\n│       └── helpers.ts           #    Reusable functions across agents\n│\n├── agents/                       # 👈 YOUR AGENTS - Business logic implementations\n│   ├── examples/                 #    Learning examples (delete when ready)\n│   └── (your agents here)        #    Each agent is a folder with:\n│                                 #    - agent.yaml (configuration)\n│                                 #    - index.ts (implementation code)\n│\n├── docs/                         # 📖 API DOCUMENTATION - First-class component\n│   ├── docs.yaml                 #    Configuration (route, UI, theme)\n│   └── *.md                      #    Markdown pages with Handlebars\n│\n├── ensembles/                    # 👈 YOUR WORKFLOWS - Orchestration definitions\n│   └── hello-world.yaml          #    YAML files defining:\n│                                 #    - flow (execution steps)\n│                                 #    - schedules (cron triggers)\n│                                 #    - webhooks (HTTP triggers)\n│                                 #    - state (shared data)\n│\n├── prompts/                      # 🔄 SHARED PROMPTS - Versioned with Edgit\n│   ├── extraction.md             #    Prompt templates that can be:\n│   └── company-analysis.md       #    - Referenced by multiple agents\n│                                 #    - Versioned independently (v1.0.0, v2.0.0)\n│                                 #    - Reused across ensembles\n│\n├── queries/                      # 🔄 SHARED SQL - Versioned with Edgit\n│   ├── company-lookup.sql        #    SQL queries that can be:\n│   └── competitor-search.sql     #    - Referenced by multiple agents\n│                                 #    - Versioned independently\n│                                 #    - Optimized over time\n│\n├── configs/                      # 🔄 SHARED CONFIGS - Versioned with Edgit\n│   └── model-settings.yaml       #    Configuration files for:\n│                                 #    - Model parameters\n│                                 #    - Feature flags\n│                                 #    - Environment-specific settings\n│\n├── schemas/                      # 🔄 SHARED SCHEMAS - Versioned with Edgit\n│   ├── invoice.json              #    JSON Schema definitions for:\n│   └── contact.json              #    - Structured AI outputs\n│                                 #    - Data validation\n│                                 #    - Versioned independently\n│\n├── wrangler.toml                 # 🔧 Cloudflare configuration\n├── package.json                  # 📦 Dependencies (@ensemble-edge/conductor, @ensemble-edge/edgit)\n├── tsconfig.json                 # TypeScript config\n└── README.md                     # Project documentation\n```\n\n**Where to add your components:**\n\n| Component | Location | Created With | Purpose |\n|-----------|----------|--------------|---------|\n| **Agents** | `agents/\u003cname\u003e/` | Create folder manually | Business logic: AI, functions, API calls, data operations |\n| **Ensembles** | `ensembles/\u003cname\u003e.yaml` | Create YAML file manually | Workflow orchestration: define flow, schedules, webhooks |\n| **Prompts** | `prompts/\u003cname\u003e.md` | Create file, register with `edgit tag` | Shared prompt templates - reusable across agents/ensembles |\n| **Queries** | `queries/\u003cname\u003e.sql` | Create file, register with `edgit tag` | Shared SQL queries - reusable, versioned, optimized |\n| **Configs** | `configs/\u003cname\u003e.yaml` | Create file, register with `edgit tag` | Shared configuration - model settings, feature flags |\n| **Schemas** | `schemas/\u003cname\u003e.json` | Create file, register with `edgit tag` | JSON Schema for structured AI outputs - versioned, validated |\n| **Utilities** | `src/lib/\u003cname\u003e.ts` | Create file | Shared helper functions across your codebase |\n| **Schedules** | Inside ensemble YAML | Add `schedules:` array | Cron-based automation (daily reports, monitoring) |\n| **Webhooks** | Inside ensemble YAML | Add `webhooks:` array | HTTP triggers (Stripe, GitHub, external events) |\n| **API Config** | `src/index.ts` | Edit file | Choose built-in API or custom endpoints |\n| **Cron Triggers** | `wrangler.toml` | Copy from ensemble schedules | Register cron expressions with Cloudflare |\n| **Environment Vars** | `wrangler.toml` | Edit `[vars]` section | API keys, settings, feature flags |\n\n**Key Concepts:**\n\n1. **Two types of components**:\n   - **Built-in** (inside Conductor) - Scoring agents, validators, etc. - updated when you upgrade Conductor\n   - **Your components** (your project) - Agents, ensembles, prompts, queries - never touched by Conductor\n\n2. **Shared, versioned components** (🔄):\n   - `prompts/`, `queries/`, `configs/` are **Edgit components**\n   - Can be referenced by multiple agents or ensembles\n   - Versioned independently (e.g., `extraction-prompt@v1.0.0`, `extraction-prompt@v2.0.0`)\n   - **Example**: 5 different agents can all use `company-analysis-prompt@v2.1.0`\n\n3. **Agent implementations** (👈):\n   - `agents/` contains your business logic code\n   - Each agent can reference shared prompts/queries at specific versions\n   - **Example**: `agent.yaml` can specify `prompt: company-analysis@v2.1.0`\n\n4. **Workflow orchestration**:\n   - `ensembles/` defines how agents work together\n   - Can reference components directly: `component: extraction-prompt@v0.1.0`\n   - **Example**: Mix versions - use v0.1.0 of prompt (ancient but perfect) with v3.0.0 of agent (latest)\n\n5. **Multiple projects share Conductor**:\n   - Use `npx @ensemble-edge/conductor` (no installation needed)\n   - Use in many projects: `my-app-1/`, `my-app-2/`, `my-app-3/`\n   - Each project has its own agents, ensembles, prompts, queries\n\n### Add to Existing Project\n\nAlready have a Cloudflare Worker? Add Conductor to it:\n\n```bash\n# 1. Install conductor\nnpm install @ensemble-edge/conductor\n\n# 2. Create directories\nmkdir -p agents ensembles\n\n# 3. Add your first agent\nensemble conductor add agent greet --operation code\n\n# 4. Create an ensemble\n# Create ensembles/hello-world.yaml manually or use ensemble conductor add ensemble\n\n# 5. Update your worker\n```\n\nThen in your worker (`src/index.ts`):\n\n```typescript\nimport { Executor, MemberLoader } from '@ensemble-edge/conductor';\nimport greetConfig from '../agents/greet/agent.yaml';\nimport greetImpl from '../agents/greet';\n\nexport default {\n  async fetch(request: Request, env: Env, ctx: ExecutionContext) {\n    const executor = new Executor({ env, ctx });\n\n    const loader = new MemberLoader({ env, ctx });\n    const greet = loader.registerMember(greetConfig, greetImpl);\n    executor.registerMember(greet);\n\n    // Your logic here\n    return Response.json({ status: 'ok' });\n  }\n};\n```\n\n**That's it!** Conductor doesn't require a specific project structure - just install and use.\n\n## Architecture\n\nConductor is a single npm package with three parts:\n\n```\n@ensemble-edge/conductor\n├── Runtime      - Core orchestration engine (Executor, Parser, StateManager, Durable Objects)\n├── CLI          - Project management tools (init, add member, validate, upgrade)\n└── SDK          - Development utilities (client, testing, member factories)\n```\n\n## Runtime API\n\n### Basic Usage\n\n```typescript\nimport { Executor, MemberLoader } from '@ensemble-edge/conductor';\n\nexport default {\n  async fetch(request: Request, env: Env, ctx: ExecutionContext) {\n    // Create executor\n    const executor = new Executor({ env, ctx });\n\n    // Register your agents\n    const loader = new MemberLoader({ env, ctx });\n    const greet = loader.registerMember(greetConfig, greetFunction);\n    executor.registerMember(greet);\n\n    // Execute ensemble\n    const input = await request.json();\n    const result = await executor.executeFromYAML(yamlContent, input);\n\n    return Response.json(result);\n  }\n};\n```\n\n### Using Durable Objects\n\nExport Durable Objects in your worker:\n\n```typescript\nimport { Executor, ExecutionState, HITLState } from '@ensemble-edge/conductor';\n\nexport default {\n  async fetch(request: Request, env: Env, ctx: ExecutionContext) {\n    const executor = new Executor({ env, ctx });\n    // ... your logic\n  }\n};\n\n// Export Durable Objects\nexport { ExecutionState, HITLState };\n```\n\n## Testing\n\nConductor provides **first-class testing support** built into the core package. No separate testing package needed - comprehensive testing utilities are included.\n\n### Test Infrastructure\n\n**1500+ tests passing** covering:\n- 🧪 **Unit Tests** - Core runtime, agents, state management, pages\n- 🔗 **Integration Tests** - End-to-end workflows, catalog loading, dynamic routing\n\n**Test Coverage:**\n- Lines: 40%+ | Functions: 40%+ | Branches: 35%+ | Statements: 40%+\n- Comprehensive coverage of critical paths with mock implementations\n\n### Quick Start\n\n```bash\n# Run all tests\nnpm test\n\n# Run with coverage\nnpm run test:coverage\n\n# Watch mode\nnpm run test:watch\n```\n\n### Writing Tests\n\nImport testing utilities from `@ensemble-edge/conductor/testing`:\n\n```typescript\nimport { describe, it, expect } from 'vitest';\nimport { TestConductor, registerMatchers } from '@ensemble-edge/conductor/testing';\n\n// Register custom matchers\nregisterMatchers();\n\ndescribe('My Ensemble', () =\u003e {\n  it('should execute successfully', async () =\u003e {\n    // Create test conductor\n    const conductor = await TestConductor.create({\n      projectPath: '.'\n    });\n\n    // Execute ensemble\n    const result = await conductor.executeEnsemble('hello-world', {\n      name: 'World'\n    });\n\n    // Use custom matchers\n    expect(result).toBeSuccessful();\n    expect(result).toHaveOutput({ message: 'Hello, World!' });\n  });\n});\n```\n\n### Custom Matchers\n\nConductor provides specialized matchers for testing workflows:\n\n```typescript\n// Success/failure assertions\nexpect(result).toBeSuccessful();\nexpect(result).toBeFailed();\n\n// Output assertions\nexpect(result).toHaveOutput({ key: 'value' });\nexpect(result).toHaveOutputContaining({ key: 'value' });\n\n// State assertions\nexpect(result).toHaveState({ counter: 5 });\n\n// Member execution\nexpect(result).toHaveMemberExecuted('member-name');\nexpect(result).toHaveMemberFailed('member-name');\n\n// Timing assertions\nexpect(result).toHaveCompletedWithin(1000); // ms\n```\n\n### Mock Providers\n\nTest without external dependencies using built-in mocks:\n\n```typescript\nimport {\n  mockAIProvider,\n  mockDatabase,\n  mockHTTP,\n  mockVectorize\n} from '@ensemble-edge/conductor/testing';\n\ndescribe('Think Member', () =\u003e {\n  it('should call AI provider', async () =\u003e {\n    const aiMock = mockAIProvider({\n      response: { message: 'AI response' }\n    });\n\n    const conductor = await TestConductor.create({\n      projectPath: '.',\n      mocks: { ai: aiMock }\n    });\n\n    const result = await conductor.executeMember('analyze', {\n      text: 'Sample text'\n    });\n\n    expect(aiMock.calls).toHaveLength(1);\n    expect(result.output.message).toBe('AI response');\n  });\n});\n```\n\n**Available Mocks:**\n- `MockAIProvider` - Mock AI/LLM responses (Think agents)\n- `MockDatabase` - Mock KV/D1/R2 operations (Data agents)\n- `MockHTTPClient` - Mock HTTP requests (HTTP agents)\n- `MockVectorize` - Mock vector search (RAG agents)\n- `MockDurableObject` - Mock Durable Object state\n\n### Test Configuration\n\nConfigure testing with `vitest.config.mts`:\n\n```typescript\nimport { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';\n\nexport default defineWorkersConfig({\n  test: {\n    poolOptions: {\n      workers: {\n        wrangler: { configPath: './wrangler.jsonc' }\n      }\n    },\n    testTimeout: 15000, // 15 seconds for Worker operations\n    coverage: {\n      provider: 'v8',\n      reporter: ['text', 'json', 'html', 'lcov'],\n      thresholds: {\n        lines: 40,\n        functions: 40,\n        branches: 35,\n        statements: 40\n      }\n    }\n  }\n});\n```\n\n### Testing Patterns\n\n#### Unit Testing Members\n\n```typescript\nimport { APIMember } from '@ensemble-edge/conductor';\n\ndescribe('API Member', () =\u003e {\n  it('should make HTTP request', async () =\u003e {\n    const mockFetch = vi.fn().mockResolvedValue({\n      ok: true,\n      json: async () =\u003e ({ data: 'response' })\n    });\n    global.fetch = mockFetch;\n\n    const member = new APIMember({\n      name: 'test-api',\n      type: 'API',\n      config: { url: 'https://api.example.com/data' }\n    });\n\n    const result = await member.execute({\n      input: {},\n      env: {} as any,\n      ctx: {} as ExecutionContext\n    });\n\n    expect(result.success).toBe(true);\n    expect(mockFetch).toHaveBeenCalledWith(\n      'https://api.example.com/data',\n      expect.any(Object)\n    );\n  });\n});\n```\n\n#### Integration Testing Workflows\n\n```typescript\ndescribe('Company Intelligence Workflow', () =\u003e {\n  it('should fetch and analyze company data', async () =\u003e {\n    const conductor = await TestConductor.create({\n      projectPath: '.',\n      mocks: {\n        http: mockHTTP({\n          'https://api.example.com/company': {\n            data: { name: 'Acme Corp' }\n          }\n        }),\n        ai: mockAIProvider({\n          response: { analysis: 'Strong company' }\n        })\n      }\n    });\n\n    const result = await conductor.executeEnsemble('company-intelligence', {\n      domain: 'acme.com'\n    });\n\n    expect(result).toBeSuccessful();\n    expect(result).toHaveOutput({\n      analysis: 'Strong company'\n    });\n    expect(result).toHaveMemberExecuted('fetch-company-data');\n    expect(result).toHaveMemberExecuted('analyze-company');\n  });\n});\n```\n\n#### State Management Testing\n\n```typescript\ndescribe('StateManager', () =\u003e {\n  it('should track state access', () =\u003e {\n    const manager = new StateManager({\n      initial: { counter: 0, name: 'test' }\n    });\n\n    const { getPendingUpdates } = manager.getStateForMember('member1', {\n      use: ['counter'],\n      set: ['counter']\n    });\n\n    const { newLog } = getPendingUpdates();\n    const manager2 = manager.applyPendingUpdates({}, newLog);\n\n    const report = manager2.getAccessReport();\n    expect(report.accessPatterns['member1']).toBeDefined();\n    expect(report.unusedKeys).toContain('name');\n  });\n});\n```\n\n### Test Structure\n\nRecommended project test structure:\n\n```\nmy-project/\n├── tests/\n│   ├── unit/              # Unit tests for individual agents\n│   │   ├── agents/\n│   │   │   ├── greet.test.ts\n│   │   │   ├── analyze.test.ts\n│   │   │   └── fetch-data.test.ts\n│   │   └── runtime/\n│   │       ├── parser.test.ts\n│   │       ├── interpolation.test.ts\n│   │       └── state-manager.test.ts\n│   │\n│   └── integration/       # Integration tests for workflows\n│       ├── hello-world.test.ts\n│       ├── company-intel.test.ts\n│       └── approval-workflow.test.ts\n│\n├── vitest.config.mts      # Test configuration\n└── package.json\n```\n\n### Continuous Integration\n\nAdd to your CI pipeline:\n\n```yaml\n# .github/workflows/test.yml\nname: Test\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n      - run: npm install\n      - run: npm test\n      - run: npm run test:coverage\n```\n\n### Debugging Tests\n\n```bash\n# Run specific test file\nnpx vitest run tests/unit/members/greet.test.ts\n\n# Run tests matching pattern\nnpx vitest run -t \"should execute\"\n\n# Debug mode\nnpx vitest --inspect-brk\n\n# UI mode\nnpx vitest --ui\n```\n\n### Best Practices\n\n1. **Test at the right level**\n   - Unit tests: Individual agents, utilities, parsing\n   - Integration tests: Complete workflows, state flow\n\n2. **Use mocks for external dependencies**\n   - Mock AI providers to avoid API costs\n   - Mock databases to avoid state pollution\n   - Mock HTTP to avoid network flakiness\n\n3. **Test critical paths first**\n   - Agent execution\n   - State management\n   - Error handling\n   - Configuration parsing\n\n4. **Keep tests fast**\n   - Use mocks instead of real services\n   - Run in Workers pool for realistic execution\n   - Set appropriate timeouts (15s default)\n\n5. **Maintain test coverage**\n   - Aim for 40%+ coverage on critical code\n   - Focus on business logic, not boilerplate\n   - Use coverage reports to find gaps\n\n## SDK Usage\n\n### Agent Development\n\n```typescript\nimport { createFunctionAgent } from '@ensemble-edge/conductor/sdk';\n\nexport default createFunctionAgent({\n  async handler({ input }) {\n    return {\n      message: `Hello, ${input.name}!`\n    };\n  }\n});\n```\n\n### Client Library (Call Deployed Conductors)\n\n```typescript\nimport { ConductorClient } from '@ensemble-edge/conductor/sdk';\n\n// Connect to your deployed conductor\nconst client = new ConductorClient({\n  baseUrl: 'https://my-project.example.com',\n  apiKey: process.env.API_KEY\n});\n\n// Execute an ensemble\nconst result = await client.executeEnsemble('company-intelligence', {\n  domain: 'acme.com'\n});\n\n// Stream results\nfor await (const chunk of client.streamEnsemble('analysis', input)) {\n  console.log(chunk);\n}\n```\n\n### Testing Utilities\n\nSee the [Testing](#testing) section above for comprehensive testing examples using TestConductor, custom matchers, and mock providers.\n\n```typescript\nimport { TestConductor } from '@ensemble-edge/conductor/testing';\n\ndescribe('greet agent', () =\u003e {\n  it('should greet user', async () =\u003e {\n    const conductor = await TestConductor.create({ projectPath: '.' });\n\n    const result = await conductor.executeAgent('greet', {\n      name: 'World'\n    });\n\n    expect(result.success).toBe(true);\n    expect(result.output.message).toBe('Hello, World!');\n  });\n});\n```\n\n### Schema Validation\n\nUse JSON Schema in your agent.yaml for input/output validation:\n\n```yaml\n# agents/my-agent/agent.yaml\nname: my-agent\noperation: code\nschema:\n  input:\n    type: object\n    properties:\n      domain:\n        type: string\n      required:\n        type: boolean\n    required: [domain]\n  output:\n    type: object\n    properties:\n      result:\n        type: string\n```\n\nConductor automatically validates inputs/outputs against your schema at runtime.\n\n## CLI Commands\n\nAll CLI commands are available through the unified Ensemble CLI:\n\n### `ensemble conductor init \u003cname\u003e`\n\nCreate a new Conductor project\n\n```bash\nensemble conductor init my-project\ncd my-project\npnpm install\nensemble conductor start\n```\n\n\u003e **Tip**: For CI/automated environments, use `--yes` to skip all interactive prompts:\n\u003e ```bash\n\u003e ensemble conductor init my-project --yes\n\u003e ```\n\nCreates a complete project with:\n- 10 example pages (static, dynamic, forms)\n- Working hello-world ensemble\n- Example agents with implementations\n- Test suite with passing examples\n\n### `ensemble conductor add agent \u003cname\u003e`\n\nScaffold a new agent (works in any project with Conductor installed)\n\n```bash\nensemble conductor add agent analyze-company --operation think\nensemble conductor add agent fetch-data --operation http\nensemble conductor add agent calculate --operation code\n\n# Create think agent with Edgit-ready prompt\nensemble conductor add agent analyze-company --operation think --with-prompt\n```\n\n**Options:**\n- `-o, --operation \u003ctype\u003e` - Agent operation type (code, think, http, data, storage, etc.)\n- `-d, --description \u003cdesc\u003e` - Agent description\n- `--with-prompt` - Create prompt.md file for think agents (Edgit integration)\n\nCreates:\n- `agents/\u003cname\u003e/agent.yaml` - Configuration\n- `agents/\u003cname\u003e/index.ts` - Implementation template\n- `agents/\u003cname\u003e/prompt.md` - Prompt template (with --with-prompt)\n\n### `ensemble conductor validate`\n\nValidate YAML syntax and agent references\n\n```bash\nensemble conductor validate\n```\n\nChecks:\n- ✅ All agent.yaml files are valid YAML\n- ✅ Required fields present (name, operation)\n- ✅ Ensemble agent references exist\n- ✅ Schema compliance\n\n### `ensemble conductor upgrade`\n\nUpgrade Conductor and run migrations\n\n```bash\nensemble conductor upgrade\n# or skip confirmation\nensemble conductor upgrade --yes\n```\n\nWhat it does:\n- 📡 Checks for latest version\n- 📦 Updates package\n- 🔧 Runs migration scripts\n- ✅ Verifies configuration\n\n## Agent Types\n\n### Code Agent\nExecute JavaScript/TypeScript functions\n\n```yaml\n# agents/user/calculate/agent.yaml\nname: calculate\noperation: code\nhandler: ./index.ts\ndescription: Calculate score\n\nschema:\n  input:\n    value: number\n  output:\n    score: number\n```\n\n### Think Agent\nAI reasoning with LLMs (OpenAI, Anthropic, Cloudflare AI)\n\n```yaml\n# agents/user/analyze/agent.yaml\nname: analyze\noperation: think\nhandler: ./index.ts\ndescription: Analyze data with AI\n\nconfig:\n  model: gpt-4\n  provider: openai\n  temperature: 0.7\n```\n\n### Storage Agent\nStorage operations with KV, D1, or R2\n\n```yaml\n# agents/user/cache-lookup/agent.yaml\nname: cache-lookup\noperation: storage\nhandler: ./index.ts\ndescription: Look up cached data\n\nconfig:\n  storage: kv\n  operation: get\n  binding: CACHE\n```\n\n### HTTP Agent\nHTTP requests to external services\n\n```yaml\n# agents/user/fetch-data/agent.yaml\nname: fetch-data\noperation: http\nhandler: ./index.ts\ndescription: Fetch external data\n\nconfig:\n  url: https://api.example.com/data\n  method: GET\n  headers:\n    Authorization: Bearer ${env.API_KEY}\n```\n\n## Ensembles\n\nDefine workflows as YAML:\n\n```yaml\nname: company-intelligence\ndescription: Analyze company data\n\ntrigger:\n  - type: http\n    path: /api/intelligence\n    methods: [POST]\n    public: true\n\nflow:\n  - name: fetch-company-data\n    agent: fetch-company-data\n    input:\n      domain: ${input.domain}\n    cache:\n      ttl: 3600\n\n  - name: analyze-company\n    agent: analyze-company\n    input:\n      companyData: ${fetch-company-data.output}\n      instructions: Analyze this company\n\noutput:\n  analysis: ${analyze-company.output.analysis}\n```\n\n## Scheduled Execution\n\nSchedule ensembles to run automatically using cron expressions. Perfect for periodic data processing, monitoring, reports, and automated workflows.\n\n### Configuration\n\nAdd schedules to your ensemble YAML:\n\n```yaml\nname: daily-report\ndescription: Generate daily analytics report\n\nschedules:\n  - cron: \"0 9 * * *\"           # Every day at 9 AM UTC\n    timezone: \"America/New_York\" # Optional: timezone for cron\n    enabled: true\n    input:\n      reportType: \"daily\"\n      recipients: [\"team@example.com\"]\n\n  - cron: \"0 */4 * * *\"          # Every 4 hours\n    enabled: true\n    input:\n      reportType: \"hourly\"\n\nflow:\n  - name: generate-report\n    agent: generate-report\n    input:\n      type: ${input.reportType}\n```\n\n### Cron Expression Format\n\nStandard Unix cron syntax:\n\n```\n┌─────── minute (0 - 59)\n│ ┌────── hour (0 - 23)\n│ │ ┌───── day of month (1 - 31)\n│ │ │ ┌──── month (1 - 12)\n│ │ │ │ ┌─── day of week (0 - 7) (Sunday = 0 or 7)\n│ │ │ │ │\n* * * * *\n```\n\n**Examples:**\n- `\"0 9 * * *\"` - Daily at 9 AM UTC\n- `\"*/15 * * * *\"` - Every 15 minutes\n- `\"0 0 * * 0\"` - Weekly on Sunday at midnight\n- `\"0 0 1 * *\"` - Monthly on the 1st at midnight\n- `\"0 */6 * * *\"` - Every 6 hours\n\n### Worker Configuration\n\nAdd cron triggers to your `wrangler.toml`:\n\n```toml\n[triggers]\ncrons = [\n  \"0 9 * * *\",      # Daily at 9 AM UTC\n  \"0 */4 * * *\",    # Every 4 hours\n  \"*/15 * * * *\"    # Every 15 minutes\n]\n```\n\n**Automatic generation**: Get all cron expressions from your ensembles:\n\n```bash\ncurl https://your-worker.dev/api/v1/schedules/crons/list\n```\n\n### Runtime Behavior\n\nWhen a cron trigger fires:\n\n1. ScheduleManager loads all ensembles with matching cron expressions\n2. Each matching schedule executes with its configured input\n3. Execution includes `_schedule` metadata:\n\n```json\n{\n  \"reportType\": \"daily\",\n  \"_schedule\": {\n    \"cron\": \"0 9 * * *\",\n    \"timezone\": \"America/New_York\",\n    \"scheduledTime\": 1699524000000,\n    \"triggeredAt\": 1699524001234\n  }\n}\n```\n\n### Testing Schedules\n\nUse the API to test schedules without waiting for cron:\n\n```bash\n# Trigger a specific ensemble's schedule manually\ncurl -X POST https://your-worker.dev/api/v1/schedules/daily-report/trigger \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"scheduleIndex\": 0}'\n\n# Test a cron expression\ncurl -X POST https://your-worker.dev/api/v1/schedules/test \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"cron\": \"0 9 * * *\", \"timezone\": \"America/New_York\"}'\n```\n\n## Webhooks\n\nTrigger ensemble execution via HTTP webhooks. Perfect for integrations, event-driven workflows, and external system notifications.\n\n### Configuration\n\nAdd webhooks to your ensemble YAML:\n\n```yaml\nname: process-payment\ndescription: Process payment webhook from Stripe\n\nwebhooks:\n  - path: \"/stripe-payment\"\n    method: POST\n    auth:\n      type: signature\n      secret: ${env.STRIPE_WEBHOOK_SECRET}\n    async: true              # Return immediately, execute in background\n    timeout: 30000          # 30 second timeout\n\n  - path: \"/github-push\"\n    method: POST\n    auth:\n      type: bearer\n      secret: ${env.GITHUB_TOKEN}\n    mode: trigger           # 'trigger' (default) or 'resume' (HITL)\n\nflow:\n  - name: validate-payment\n    agent: validate-payment\n    input:\n      paymentData: ${input.data}\n```\n\n### Authentication Types\n\n**Bearer Token:**\n```yaml\nauth:\n  type: bearer\n  secret: ${env.API_SECRET}\n```\n\nRequest requires: `Authorization: Bearer \u003csecret\u003e`\n\n**Signature Verification:**\n```yaml\nauth:\n  type: signature\n  secret: ${env.WEBHOOK_SECRET}\n```\n\nValidates `X-Signature` header (Stripe-style HMAC).\n\n**Basic Auth:**\n```yaml\nauth:\n  type: basic\n  secret: ${env.BASIC_AUTH_CREDENTIALS}\n```\n\nRequest requires: `Authorization: Basic \u003cbase64(username:password)\u003e`\n\n### Webhook Modes\n\n**Trigger Mode** (default): Start new execution\n```yaml\nmode: trigger\n```\n\n**Resume Mode**: Resume HITL workflow\n```yaml\nmode: resume\n```\n\nUsed with Human-in-the-Loop workflows to resume after approval.\n\n### Webhook URLs\n\nWebhooks are available at:\n\n```\nhttps://your-worker.dev/webhooks/{path}\n```\n\nExample:\n```bash\ncurl -X POST https://your-worker.dev/webhooks/stripe-payment \\\n  -H \"Authorization: Bearer your-secret\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"amount\": 1000, \"currency\": \"usd\"}'\n```\n\n### Async Execution\n\nSet `async: true` for long-running workflows:\n\n```yaml\nwebhooks:\n  - path: \"/long-process\"\n    async: true\n```\n\nReturns immediately with execution ID:\n\n```json\n{\n  \"status\": \"accepted\",\n  \"executionId\": \"exec_abc123\",\n  \"statusUrl\": \"/api/v1/executions/exec_abc123\"\n}\n```\n\n## Durable Objects \u0026 Stateful Workflows\n\nConductor uses Cloudflare Durable Objects for strongly consistent, stateful workflow tracking. Two Durable Object types provide different state management patterns.\n\n### ExecutionState\n\nTracks async execution state with real-time status queries and optional WebSocket streaming.\n\n**Use cases:**\n- Long-running workflow monitoring\n- Real-time progress updates\n- Execution history and metrics\n- Status dashboards\n\n**Configuration:**\n\n```toml\n# wrangler.toml\n[[durable_objects.bindings]]\nname = \"EXECUTION_STATE\"\nclass_name = \"ExecutionState\"\nscript_name = \"conductor\"\n\n[[migrations]]\ntag = \"v1\"\nnew_classes = [\"ExecutionState\"]\n```\n\n**Usage:**\n\n```typescript\nimport { Executor } from '@ensemble-edge/conductor';\n\nexport default {\n  async fetch(request: Request, env: Env, ctx: ExecutionContext) {\n    const executor = new Executor({ env, ctx });\n\n    // Start async execution with tracking\n    const executionId = crypto.randomUUID();\n    const result = await executor.executeEnsemble(ensemble, input, {\n      async: true,\n      executionId\n    });\n\n    // Return execution ID for status tracking\n    return Response.json({\n      executionId,\n      statusUrl: `/api/v1/executions/${executionId}`\n    });\n  }\n};\n```\n\n**Query execution status:**\n\n```bash\n# Get current status\ncurl https://your-worker.dev/api/v1/executions/exec_abc123\n\n# Stream live updates via WebSocket\nwscat -c wss://your-worker.dev/api/v1/executions/exec_abc123/stream\n```\n\n**Status response:**\n\n```json\n{\n  \"executionId\": \"exec_abc123\",\n  \"ensembleName\": \"process-payment\",\n  \"status\": \"running\",\n  \"startedAt\": 1699524000000,\n  \"currentStep\": \"validate-payment\",\n  \"stepIndex\": 2,\n  \"totalSteps\": 5,\n  \"outputs\": {\n    \"fetch-data\": { \"result\": \"...\" }\n  },\n  \"metrics\": {\n    \"duration\": 1234,\n    \"stepsCompleted\": 2\n  }\n}\n```\n\n### HITLState (Human-in-the-Loop)\n\nManages approval workflows and human intervention points with resumption support.\n\n**Use cases:**\n- Approval workflows\n- Human review gates\n- Manual intervention points\n- Compliance checkpoints\n\n**Configuration:**\n\n```toml\n# wrangler.toml\n[[durable_objects.bindings]]\nname = \"HITL_STATE\"\nclass_name = \"HITLState\"\nscript_name = \"conductor\"\n\n[[migrations]]\ntag = \"v1\"\nnew_classes = [\"ExecutionState\", \"HITLState\"]\n```\n\n**Ensemble configuration:**\n\n```yaml\nname: expense-approval\ndescription: Expense approval workflow with human review\n\nflow:\n  - name: validate-expense\n    agent: validate-expense\n    input:\n      expense: ${input.expense}\n\n  - name: request-approval\n    agent: request-approval\n    operation: hitl\n    input:\n      requester: ${input.userId}\n      amount: ${input.expense.amount}\n      reason: ${input.expense.reason}\n      approvers: [\"manager@example.com\"]\n      timeout: 86400000  # 24 hours\n\n  - name: process-approved-expense\n    agent: process-approved-expense\n    input:\n      expense: ${input.expense}\n```\n\n**HITL workflow:**\n\n1. Execution pauses at HITL step\n2. Approval request sent to designated approvers\n3. Execution waits for approval/rejection\n4. Resume with decision\n\n**Resume execution:**\n\n```bash\n# Approve\ncurl -X POST https://your-worker.dev/api/v1/executions/exec_abc123/resume \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"approved\": true, \"comment\": \"Looks good\"}'\n\n# Reject\ncurl -X POST https://your-worker.dev/api/v1/executions/exec_abc123/resume \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"approved\": false, \"comment\": \"Needs more detail\"}'\n```\n\n**HITL state:**\n\n```json\n{\n  \"executionId\": \"exec_abc123\",\n  \"status\": \"pending_approval\",\n  \"requestedAt\": 1699524000000,\n  \"approvers\": [\"manager@example.com\"],\n  \"timeout\": 86400000,\n  \"context\": {\n    \"amount\": 1000,\n    \"reason\": \"Conference travel\"\n  }\n}\n```\n\n### Storage Backend\n\nDurable Objects use Cloudflare's strongly consistent storage:\n\n- **Single-threaded execution** - No race conditions\n- **Transactional storage** - Atomic state updates\n- **Global uniqueness** - One instance per execution ID\n- **Automatic failover** - Cloudflare handles migration\n- **Low latency** - Co-located with execution\n\n## API Endpoints\n\nConductor provides a comprehensive REST API for workflow management.\n\n### Base Configuration\n\n```toml\n# wrangler.toml\n[vars]\nAPI_KEYS = \"key1,key2,key3\"        # Optional: API key authentication\nALLOW_ANONYMOUS = \"false\"          # Require authentication\nDISABLE_LOGGING = \"false\"          # Enable request logging\n```\n\n### Execution API\n\n**POST /api/v1/execute**\n\nExecute an ensemble:\n\n```bash\ncurl -X POST https://your-worker.dev/api/v1/execute \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-API-Key: your-api-key\" \\\n  -d '{\n    \"ensemble\": \"company-intelligence\",\n    \"input\": {\n      \"domain\": \"acme.com\"\n    },\n    \"async\": true\n  }'\n```\n\nResponse:\n\n```json\n{\n  \"executionId\": \"exec_abc123\",\n  \"status\": \"accepted\",\n  \"statusUrl\": \"/api/v1/executions/exec_abc123\"\n}\n```\n\n**GET /api/v1/executions/:id**\n\nGet execution status:\n\n```bash\ncurl https://your-worker.dev/api/v1/executions/exec_abc123 \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n**POST /api/v1/executions/:id/resume**\n\nResume HITL execution:\n\n```bash\ncurl -X POST https://your-worker.dev/api/v1/executions/exec_abc123/resume \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-API-Key: your-api-key\" \\\n  -d '{\"approved\": true, \"comment\": \"Approved\"}'\n```\n\n**DELETE /api/v1/executions/:id**\n\nCancel execution:\n\n```bash\ncurl -X DELETE https://your-worker.dev/api/v1/executions/exec_abc123 \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n### Schedule API\n\n**GET /api/v1/schedules**\n\nList all scheduled ensembles:\n\n```bash\ncurl https://your-worker.dev/api/v1/schedules \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n**GET /api/v1/schedules/:ensembleName**\n\nGet schedules for specific ensemble:\n\n```bash\ncurl https://your-worker.dev/api/v1/schedules/daily-report \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n**POST /api/v1/schedules/:ensembleName/trigger**\n\nManually trigger a schedule:\n\n```bash\ncurl -X POST https://your-worker.dev/api/v1/schedules/daily-report/trigger \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-API-Key: your-api-key\" \\\n  -d '{\"scheduleIndex\": 0}'\n```\n\n**GET /api/v1/schedules/crons/list**\n\nGet all cron expressions for wrangler.toml:\n\n```bash\ncurl https://your-worker.dev/api/v1/schedules/crons/list \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n### Member API\n\n**GET /api/v1/agents**\n\nList available agents:\n\n```bash\ncurl https://your-worker.dev/api/v1/agents \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n**GET /api/v1/agents/:name**\n\nGet agent details:\n\n```bash\ncurl https://your-worker.dev/api/v1/agents/analyze-company \\\n  -H \"X-API-Key: your-api-key\"\n```\n\n### Stream API\n\n**POST /api/v1/stream**\n\nStream ensemble execution with Server-Sent Events:\n\n```bash\ncurl -N -X POST https://your-worker.dev/api/v1/stream \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-API-Key: your-api-key\" \\\n  -d '{\n    \"ensemble\": \"analysis\",\n    \"input\": {\"data\": \"...\"}\n  }'\n```\n\n### Health API\n\n**GET /health**\n\nHealth check (no authentication required):\n\n```bash\ncurl https://your-worker.dev/health\n```\n\nResponse:\n\n```json\n{\n  \"status\": \"healthy\",\n  \"timestamp\": 1699524000000,\n  \"version\": \"1.0.0\"\n}\n```\n\n### Webhook API\n\n**POST /webhooks/:path**\n\nTrigger webhook (authentication per webhook config):\n\n```bash\ncurl -X POST https://your-worker.dev/webhooks/stripe-payment \\\n  -H \"Authorization: Bearer your-webhook-secret\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"event\": \"payment.succeeded\"}'\n```\n\n## Platform Architecture\n\nConductor uses a **three-layer architecture** that cleanly separates AI providers, cloud platforms, and core interfaces. This design allows you to use any AI model from any provider while leveraging platform-specific features like Cloudflare's AI Gateway.\n\n### Three Layers\n\n```\ncatalog/                   # Reference data (packaged with npm)\n├── ai/                    # Layer 1: AI provider catalogs\n│   ├── manifest.json\n│   ├── workers-ai.json\n│   ├── openai.json\n│   ├── anthropic.json\n│   └── groq.json\n└── cloud/                 # Layer 2: Cloud platform configs\n    └── cloudflare/\n        ├── ai-gateway.json\n        ├── capabilities.json\n        └── bindings.json\n\nsrc/platforms/             # TypeScript source (compiles to dist)\n├── base/                  # Layer 3: Core interfaces\n│   ├── platform.ts\n│   ├── types.ts\n│   └── index.ts\n└── cloudflare/            # Cloudflare adapter\n    └── index.ts\n```\n\n#### Layer 1: AI Providers (`catalog/ai/`)\n**AI provider catalogs** containing model lists, capabilities, and deprecation tracking. Each provider is a separate JSON file:\n\n- **workers-ai.json** - Cloudflare's edge-hosted models (Llama, Mistral, Qwen, etc.)\n- **openai.json** - GPT-4, GPT-4o, GPT-3.5, o1\n- **anthropic.json** - Claude 3.5, Claude 3\n- **groq.json** - Ultra-fast inference (Llama, Mixtral, Gemma)\n- **manifest.json** - Registry of all providers with default routing\n\n**Key insight**: Workers AI is just another AI provider, not a special platform feature. All providers are treated equally in configuration.\n\n#### Layer 2: Cloud Platforms (`catalog/cloud/`)\n**Infrastructure platform configurations** like Cloudflare Workers, including:\n\n- AI Gateway configuration (routing, caching, analytics)\n- Platform capabilities (bindings, storage, compute)\n- Platform-specific features\n\n#### Layer 3: Base Interfaces (`src/platforms/base/`)\n**TypeScript interfaces and types** that all platform adapters must implement. Source code that compiles to `dist/platforms/base/`:\n\n- **platform.ts** - BasePlatform abstract class\n- **types.ts** - PlatformModel, PlatformProvider, ValidationResult types\n- **index.ts** - Public exports\n\n### AI Provider Configuration\n\nEach AI provider file contains:\n\n```json\n{\n  \"provider\": \"openai\",\n  \"name\": \"OpenAI\",\n  \"defaultRouting\": \"cloudflare-gateway\",\n  \"endpoints\": {\n    \"direct\": {\n      \"baseUrl\": \"https://api.openai.com/v1\"\n    },\n    \"cloudflare-gateway\": {\n      \"baseUrl\": \"https://gateway.ai.cloudflare.com/v1/{account}/{gateway}/openai\",\n      \"features\": [\"caching\", \"rate-limiting\", \"analytics\"]\n    }\n  },\n  \"models\": [\n    {\n      \"id\": \"gpt-4o\",\n      \"name\": \"GPT-4o\",\n      \"status\": \"active\",\n      \"capabilities\": [\"chat\", \"vision\", \"function-calling\"],\n      \"contextWindow\": 128000,\n      \"recommended\": true\n    }\n  ]\n}\n```\n\n### Routing Modes\n\nConductor supports **three routing modes** for accessing AI models:\n\n#### 1. `cloudflare` - Platform-Native (Workers AI only)\nDirect access to Cloudflare's edge-hosted models via Workers AI binding.\n\n```yaml\n# agents/user/analyze/agent.yaml\noperation: think\nconfig:\n  provider: workers-ai\n  model: \"@cf/meta/llama-3.1-8b-instruct\"\n  routing: cloudflare  # Platform-native\n```\n\n**Benefits**: Ultra-low latency, no API keys needed, edge execution\n\n#### 2. `cloudflare-gateway` - AI Gateway (Recommended for external providers)\nRoute external provider requests through Cloudflare AI Gateway for caching, analytics, and cost controls.\n\n```yaml\n# agents/user/analyze/agent.yaml\noperation: think\nconfig:\n  provider: openai\n  model: gpt-4o\n  routing: cloudflare-gateway  # Default for external providers\n```\n\n**Benefits**:\n- Persistent caching (reduce costs \u0026 latency)\n- Real-time analytics and logging\n- Rate limiting and cost controls\n- Single API for multiple providers\n- Automatic failover and retries\n\n#### 3. `direct` - Direct API Calls\nMake direct API calls to the provider, bypassing the gateway.\n\n```yaml\n# agents/user/analyze/agent.yaml\noperation: think\nconfig:\n  provider: anthropic\n  model: claude-3-5-sonnet-20241022\n  routing: direct  # Bypass gateway\n```\n\n**Use when**: You need provider-specific features not available through the gateway, or you're not on Cloudflare.\n\n### Smart Routing Defaults\n\nConductor uses intelligent defaults based on provider and platform:\n\n| Provider | Default Routing | Reason |\n|----------|----------------|---------|\n| workers-ai | `cloudflare` | Platform-native, edge-hosted |\n| openai | `cloudflare-gateway` | Leverage caching \u0026 analytics |\n| anthropic | `cloudflare-gateway` | Leverage caching \u0026 analytics |\n| groq | `cloudflare-gateway` | Leverage analytics |\n\n**You can override these defaults** by specifying `routing:` in your agent config.\n\n### Cloudflare AI Gateway\n\nThe AI Gateway acts as a **universal API gateway** for AI providers:\n\n```\nYour Worker → AI Gateway → AI Provider (OpenAI/Anthropic/Groq/etc.)\n                    ↓\n            [Cache/Analytics/Logs]\n```\n\n**Features**:\n- ✅ Persistent caching (same request = cached response)\n- ✅ Real-time analytics (tokens, costs, latency)\n- ✅ Rate limiting per user/endpoint\n- ✅ Cost controls and spending alerts\n- ✅ Automatic retry on failures\n- ✅ Fallback between providers\n- ✅ Request/response logging\n\n**Configuration**: Set environment variables for gateway access:\n\n```toml\n# wrangler.toml\n[vars]\nCLOUDFLARE_ACCOUNT_ID = \"your-account-id\"\nCLOUDFLARE_GATEWAY_NAME = \"your-gateway-name\"\n```\n\n### Model Deprecation Tracking\n\nConductor tracks model lifecycle and warns you about deprecated models:\n\n```bash\n$ conductor check-config\n\n⚠ agents/user/analyze/agent.yaml\n  Model \"gpt-4-turbo-preview\" is deprecated\n  Reason: Replaced by stable gpt-4-turbo release\n  End of life: 2025-04-09 (120 days)\n  → Recommended: \"gpt-4-turbo\"\n```\n\nEach model in the catalog includes:\n- `status`: \"active\" | \"deprecated\"\n- `deprecatedAt`: When deprecation was announced\n- `deprecatedReason`: Why it was deprecated\n- `replacementModel`: Recommended replacement\n- `endOfLife`: When the model will stop working\n\n**Stay up to date**: Run `conductor upgrade` to get the latest model catalogs.\n\n### Using Multiple Providers\n\nYou can mix and match providers in a single ensemble:\n\n```yaml\nname: multi-provider-analysis\nflow:\n  # Fast, cheap analysis with Workers AI\n  - name: quick-scan\n    agent: quick-scan\n    input:\n      provider: workers-ai\n      model: \"@cf/meta/llama-3.1-8b-instruct\"\n\n  # Deep analysis with Claude\n  - name: deep-analysis\n    agent: deep-analysis\n    input:\n      provider: anthropic\n      model: claude-3-5-sonnet-20241022\n      routing: cloudflare-gateway\n\n  # Reasoning with OpenAI o1\n  - name: strategic-thinking\n    agent: strategic-thinking\n    input:\n      provider: openai\n      model: o1-preview\n      routing: cloudflare-gateway\n```\n\n### Platform-Specific Features\n\nWhile AI providers are universal, platforms can provide additional features:\n\n**Cloudflare Workers**:\n- Workers AI binding for edge models\n- AI Gateway for external providers\n- KV/D1/R2 for Data agents\n- Durable Objects for state\n- Automatic global distribution\n\n**Future platforms** (Vercel, AWS Lambda, etc.) will have their own cloud platform configurations while sharing the same AI provider catalogs.\n\n### Best Practices\n\n1. **Use cloudflare-gateway by default** for external providers (OpenAI, Anthropic, Groq)\n   - Get caching, analytics, and cost controls for free\n   - No code changes needed\n\n2. **Use workers-ai for speed** when sub-50ms latency matters\n   - Edge-hosted models run closest to users\n   - No API key management needed\n\n3. **Check for deprecations regularly**\n   ```bash\n   conductor check-config\n   conductor upgrade\n   ```\n\n4. **Pin model versions in production**\n   ```yaml\n   model: claude-3-5-sonnet-20241022  # Good: Specific version\n   # vs\n   model: claude-3-5-sonnet            # Risky: Auto-updates\n   ```\n\n5. **Monitor costs with AI Gateway**\n   - View analytics in Cloudflare dashboard\n   - Set spending limits and alerts\n   - Track per-user or per-endpoint usage\n\n## Edgit Integration\n\nConductor works seamlessly with Edgit for versioning prompts, configs, and member configurations. This enables powerful versioning chains where ensembles reference versioned members, which in turn reference versioned prompts.\n\n**Key Principle: Edgit creates and manages git tags. That's it.** GitHub Actions handles deployment. Edgit is a tag manager, not a deployment tool.\n\n### Quick Example\n\n```bash\n# Version a prompt\nedgit tag create company-analysis-prompt v1.0.0\n\n# Deploy to staging\nedgit tag set company-analysis-prompt staging v1.0.0\nedgit push --tags --force\n\n# Promote to production\nedgit tag set company-analysis-prompt production v1.0.0\nedgit push --tags --force\n```\n\n### What Gets Versioned with Edgit?\n\n**Edgit Components** (versioned artifacts in shared folders):\n- ✅ **Prompts** (`prompts/*.md`) - Shared prompt templates, reusable across agents/ensembles\n- ✅ **Queries** (`queries/*.sql`) - Shared SQL queries, reusable and optimized\n- ✅ **Configs** (`configs/*.yaml`) - Shared configuration files, model settings\n- ✅ **Agent configurations** (`agents/*/agent.yaml`) - Agent config files\n\n**NOT Edgit Components** (code in your repo):\n- ❌ **Agent implementations** (`agents/*/index.ts`) - Code is git-versioned and bundled with worker\n- ❌ **Ensembles** (`ensembles/*.yaml`) - Workflow definitions live in git\n- ❌ **Worker code** (`src/*`) - Application code lives in git\n\n**Key Insight:** Prompts, queries, and configs live in **their own folders** at the project root, not inside individual agents. This enables **reuse** - multiple agents can reference the same prompt at different versions.\n\n**Example: Shared Prompt Reuse**\n\n```\nmy-project/\n├── prompts/\n│   └── company-analysis.md        # Shared prompt, versioned v1.0.0, v2.0.0, v2.1.0\n├── agents/\n│   ├── analyze-tech-company/\n│   │   └── agent.yaml             # Uses: company-analysis-prompt@v2.1.0\n│   ├── analyze-startup/\n│   │   └── agent.yaml             # Uses: company-analysis-prompt@v2.0.0\n│   └── quick-company-check/\n│       └── agent.yaml             # Uses: company-analysis-prompt@v1.0.0\n```\n\nAll three agents share the same prompt source file, but reference different versions based on what works best for their use case.\n\n### The Versioning Chain\n\nConductor supports a complete versioning chain from ensemble → agent config → prompts:\n\n```\nEnsemble (git-versioned)\n  ↓ references\nAgent Config@v1.0.0 (Edgit component)\n  ↓ references\nPrompt@v2.1.0 (Edgit component)\n```\n\n**Example:**\n\n```yaml\n# ensembles/company-intel.yaml (git)\nflow:\n  - name: analyze-company-step\n    agent: analyze-company@production\n    input:\n      domain: ${input.domain}\n```\n\n```yaml\n# agents/user/analyze-company/agent.yaml (Edgit component)\nname: analyze-company\noperation: think\nconfig:\n  model: gpt-4\n  temperature: 0.7\n  prompt: company-analysis-prompt@v2.1.0  # References versioned prompt\n```\n\n```markdown\n# prompts/company-analysis.md (Edgit component)\nYou are an expert at analyzing companies...\n```\n\n**Runtime Resolution:**\n\n1. Executor reads ensemble: `analyze-company@production`\n2. Loads agent.yaml from Edgit: `analyze-company@production` → v1.0.0\n3. Agent.yaml references: `company-analysis-prompt@v2.1.0`\n4. Loads prompt from Edgit: `company-analysis-prompt@v2.1.0`\n5. Executes bundled code (index.ts) with resolved config + prompt\n\n### Three Integration Patterns\n\n#### Pattern 1: Inline (Simple)\n\nConfig lives directly in agent.yaml - no versioning needed.\n\n```yaml\n# agents/user/simple-analysis/agent.yaml\nname: simple-analysis\noperation: think\nconfig:\n  model: gpt-4\n  temperature: 0.7\n```\n\n**Use when**: Configuration is simple and doesn't change often.\n\n#### Pattern 2: Edgit Reference (Planned)\n\n\u003e **Note**: Edgit runtime integration is planned for a future release. The API below shows the intended interface.\n\nAgent loads versioned prompt from Edgit:\n\n```typescript\n// agents/user/company-analysis/index.ts\nimport { createThinkAgent } from '@ensemble-edge/conductor/sdk';\n// import { loadComponent } from '@ensemble-edge/edgit'; // Future\n\nexport default createThinkAgent({\n  async handler({ input, env }) {\n    // Future: Load versioned prompt from Edgit\n    // const prompt = await loadComponent('company-analysis-prompt@v1.2.0', env);\n\n    // For now: Load from local file or inline\n    const prompt = \"You are an expert at analyzing companies...\";\n\n    // Use with AI\n    const response = await callAI(prompt, input);\n    return response;\n  }\n});\n```\n\n**Use when**: Prompts need versioning, testing, and independent deployment.\n\n#### Pattern 3: Co-located Development (Current Approach)\n\nDuring development, keep prompts with agents or in shared prompts directory.\n\n```bash\n# 1. Create agent with prompt file\nensemble conductor add agent analyze-company --operation think --with-prompt\n\n# 2. Develop and test locally\n# Edit agents/user/analyze-company/prompt.md\n# Edit agents/user/analyze-company/index.ts\n\n# 3. Load prompt in your agent implementation\n# Read prompt.md from filesystem or include inline in agent config\n\n# 4. Future: When Edgit integration is complete\n# Version prompt: edgit tag create analyze-company-prompt v1.0.0\n# Load at runtime: loadComponent('analyze-company-prompt@v1.0.0', env)\n```\n\n**Use when**: Starting new think agents - currently the recommended approach until Edgit runtime integration is complete.\n\n### Example: Think Agent with Edgit (Planned)\n\n\u003e **Note**: Full Edgit runtime integration coming soon. Currently use inline prompts or load from local files.\n\n```typescript\nimport { createThinkAgent } from '@ensemble-edge/conductor/sdk';\n// import { loadComponent } from '@ensemble-edge/edgit'; // Future\n\nexport default createThinkAgent({\n  async handler({ input, state, env }) {\n    // Future: Load versioned prompt from Edgit\n    // const systemPrompt = await loadComponent('analysis-system-prompt@v2.1.0', env);\n\n    // Current: Inline prompt or load from agent config\n    const systemPrompt = \"You are an expert analyst...\";\n\n    // Combine with dynamic context\n    const messages = [\n      { role: 'system', content: systemPrompt },\n      { role: 'user', content: `Analyze: ${input.data}` }\n    ];\n\n    // Use with AI provider\n    const response = await env.AI.run('@cf/meta/llama-2-7b-chat-int8', {\n      messages,\n      temperature: 0.7\n    });\n\n    return {\n      content: response.result\n    };\n  }\n});\n```\n\n### Versioning Scenarios\n\n#### Scenario 1: Deploy New Config Without Code Changes\n\n```bash\n# Update agent config with new model settings\nedgit tag create analyze-company v2.0.0\n\n# agent.yaml v2.0.0\nconfig:\n  model: gpt-4-turbo      # ← Changed\n  temperature: 0.5        # ← Changed\n  prompt: company-analysis-prompt@v2.1.0\n\n# Deploy to preview\nedgit tag set analyze-company preview v2.0.0\nedgit push --tags --force\n\n# Test, then promote to production\nedgit tag set analyze-company production v2.0.0\nedgit push --tags --force\n```\n\n**No code deploy needed!** Same bundled code, different config.\n\n#### Scenario 2: A/B Test Different Configurations\n\n```yaml\n# ensembles/company-intel.yaml\nflow:\n  # 90% use stable config\n  - name: analyze-stable\n    agent: analyze-company@v1.0.0\n    weight: 90\n    input:\n      domain: ${input.domain}\n\n  # 10% test new config\n  - name: analyze-new\n    agent: analyze-company@v2.0.0\n    weight: 10\n    input:\n      domain: ${input.domain}\n```\n\n**Test in production** with gradual rollout.\n\n#### Scenario 3: Environment-Specific Configs\n\n```bash\n# Production: Stable model, proven prompt\nedgit tag set analyze-company production v1.0.0\nedgit push --tags --force\n# v1.0.0 → model: gpt-4, prompt@v1.0.0\n\n# Staging: Latest model, new prompt\nedgit tag set analyze-company staging v2.0.0\nedgit push --tags --force\n# v2.0.0 → model: gpt-4-turbo, prompt@v2.0.0\n\n# Preview: Experimental settings\nedgit tag set analyze-company preview v3.0.0-beta\nedgit push --tags --force\n# v3.0.0-beta → model: claude-3-opus, prompt@v3.0.0-beta\n```\n\n**Same ensemble, different configs per environment:**\n\n```yaml\nflow:\n  - name: analyze-prod\n    agent: analyze-company@production  # Uses v1.0.0\n  - name: analyze-staging\n    agent: analyze-company@staging     # Uses v2.0.0\n  - name: analyze-preview\n    agent: analyze-company@preview     # Uses v3.0.0-beta\n```\n\n#### Scenario 4: Independent Rollbacks\n\n```bash\n# Rollback just the prompt (keep agent config)\nedgit tag create company-analysis-prompt v2.0.1\nedgit tag set company-analysis-prompt production v2.0.1\nedgit push --tags --force\n# agent.yaml stays at v1.0.0, uses new prompt\n\n# Rollback entire agent config\nedgit tag set analyze-company production v0.9.0\nedgit push --tags --force\n# Rolls back model, temperature, AND prompt reference\n\n# Emergency: rollback prompt instantly\nedgit tag set company-analysis-prompt production v1.0.0\nedgit push --tags --force\n```\n\n### Versioning Workflow\n\n**Development to Production:**\n\n```bash\n# 1. Create agent locally\nensemble conductor add agent analyze-company --operation think --with-prompt\n\n# 2. Develop and test with local files\n# Edit agents/user/analyze-company/agent.yaml\n# Edit agents/user/analyze-company/prompt.md\n\n# 3. Version the prompt\nedgit tag create company-analysis-prompt v1.0.0\n\n# 4. Update agent.yaml to reference versioned prompt\n# config:\n#   prompt: company-analysis-prompt@v1.0.0\n\n# 5. Version the agent config\nedgit tag create analyze-company v1.0.0\n\n# 6. Deploy to staging\nedgit tag set analyze-company staging v1.0.0\nedgit push --tags --force\n\nedgit tag set company-analysis-prompt staging v1.0.0\nedgit push --tags --force\n\n# 7. Update ensemble to use versioned agent\n# flow:\n#   - name: analyze\n#     agent: analyze-company@staging\n\n# 8. Test, then promote to production\nedgit tag set analyze-company production v1.0.0\nedgit push --tags --force\n\nedgit tag set company-analysis-prompt production v1.0.0\nedgit push --tags --force\n```\n\n## Multi-Project Workflow\n\nBuild multiple projects on Conductor:\n\n```bash\nensemble conductor init owner-oiq\nensemble conductor init owner-internal\nensemble conductor init customer-portal\n```\n\nEach project is independent with its own agents and ensembles. Conductor is just the engine.\n\n## Development\n\n```bash\n# Install dependencies\nnpm install\n\n# Build runtime + SDK\nnpm run build\n\n# Test\nnpm test\n\n# Generate Cloudflare types\nnpm run cf-typegen\n```\n\n## Philosophy\n\n- **Conductor** = The runtime engine (this package)\n- **Agents** = Your code (your repository)\n- **Ensembles** = Your workflows (your YAML)\n- **CLI** = Your development workflow\n- **SDK** = Your development utilities\n\nWe provide the tools, you provide the creativity.\n\n## Examples\n\nSee [examples/](./examples/) for:\n- Complete starter project\n- Agent implementations (all types)\n- Ensemble workflows\n- Testing examples\n\n## Links\n\n- [Full Documentation](https://docs.ensemble.ai/conductor/overview)\n- [Getting Started](https://docs.ensemble.ai/conductor/getting-started/your-first-project)\n- [Operations Reference](https://docs.ensemble.ai/conductor/operations/overview)\n- [Starter Kit](https://docs.ensemble.ai/conductor/starter-kit/overview)\n- [Examples](./examples/)\n- [Issues](https://github.com/ensemble-edge/conductor/issues)\n- [Edgit Integration](https://github.com/ensemble-edge/edgit)\n\n## License\n\nApache 2.0 - see [LICENSE](LICENSE)\n\n## Key Architectural Decisions\n\n### Versioning Strategy\n\n**Code (Git):**\n- Agent implementations (index.ts)\n- Ensemble workflows (YAML)\n- Worker entry points\n\n**Configuration (Edgit):**\n- Agent configs (agent.yaml) - Version independently\n- Prompts (prompt.md) - Version independently\n- SQL queries, templates - Version independently\n\n**Benefits:**\n- 🔄 Deploy config changes without code deploy\n- 🧪 A/B test different configurations\n- 🌍 Environment-specific settings\n- ⚡ Instant rollbacks (configs OR code)\n- 📊 Mix optimal versions from any timeline\n\n### The Power of Separation\n\n**Traditional:**\n```\nv2.0.0 deployment\n├── All code at v2.0.0\n└── All configs at v2.0.0  ❌ Locked together\n```\n\n**With Conductor + Edgit:**\n```\nYour deployment\n├── Code (bundled with worker)\n├── Agent config@v1.0.0 ✅ Independent\n├── Agent config@v2.0.0 ✅ Independent\n├── Prompt@v0.1.0 ✅ Ancient but perfect\n└── Prompt@v3.0.0 ✅ Latest\n```\n\n**Access any version, any time, in any combination.**\n\n## Trademark\n\nEnsemble® is a registered trademark of Higinio O. Maycotte.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fensemble-edge%2Fconductor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fensemble-edge%2Fconductor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fensemble-edge%2Fconductor/lists"}