Documentation Index Fetch the complete documentation index at: https://docs.ensemble.ai/llms.txt
Use this file to discover all available pages before exploring further.
Think of agents as the workers in your system - each one has a specific job and does it well.
Agent Hierarchy
Components (prompts, configs) -> Versioned artifacts
|
Agents (workers) -> Reusable logic
|
Ensembles (orchestration) -> Workflows
Anatomy of an Agent
An agent has:
Inputs : Parameters it accepts
Operation : The type of work it performs (code, think, http, etc.)
Handler (for code operations): TypeScript file that implements the logic
Outputs : Data it returns
The Handler Pattern
For custom logic, agents use the handler pattern which separates YAML contracts from TypeScript implementation:
YAML (Contract) - Declares WHAT the agent does:
# agents/my-agent/my-agent.yaml
name : my-agent
operation : code
handler : ./my-agent.ts
description : What this agent does
schema :
input :
query : string
output :
result : string
TypeScript (Implementation) - Defines HOW it works:
// agents/my-agent/my-agent.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function handler ( ctx : AgentExecutionContext ) {
const { query } = ctx . input
return { result: `Processed: ${ query } ` }
}
This pattern keeps agents:
Testable - Pure TypeScript functions
Type-safe - Full TypeScript type checking
Debuggable - Standard debugging tools work
Maintainable - Clear separation of concerns
Multi-Operation Agent Example
For agents with multiple operations, use YAML flow:
# agents/company-enricher/agent.yaml
agent : company-enricher
description : Enriches company data from multiple sources
inputs :
company_name :
type : string
required : true
include_news :
type : boolean
default : false
operations :
# Search for company
- name : search
operation : http
config :
url : https://api.duckduckgo.com/?q=${input.company_name}&format=json
# Scrape website
- name : scrape
operation : http
config :
url : ${search.output.AbstractURL}
# Extract with AI
- name : extract
operation : think
config :
provider : openai
model : gpt-4o-mini
prompt : |
Extract company info from: ${scrape.output.body}
Return JSON: {name, description, industry, founded}
# Optional news
- name : fetch-news
operation : http
condition : ${input.include_news}
config :
url : https://news-api.com?q=${input.company_name}
outputs :
company_data : ${extract.output}
news : ${fetch-news.output}
source : ${search.output.AbstractURL}
Agent Types
Conductor provides three categories of agents:
1. System Agents
Built-in agents for core infrastructure functionality.
Location : agents/system/
Available :
redirect - URL redirect service (permanent, expiring, single-use links)
docs - Documentation generation and serving
fetch - HTTP fetching with caching
validate - Data validation with multiple strategies
scrape - Web scraping with bot detection
slug - Generate URL-safe slugs
tools - MCP tools integration
queries - SQL query execution
2. Debug Agents
Development utilities for testing and debugging.
Location : agents/debug/
Available :
echo - Returns input unchanged (inspect data)
delay - Add artificial delay (simulate slow operations)
inspect-context - View execution context
3. Custom Agents
Agents you build for your specific needs.
Location : agents/user/my-agent/
Example : Company enricher, data processor, report generator
4. Pre-built Feature Agents
Ready-made agents for common workflows.
Location : Built-in, reference by name
Available :
scraper - Web scraping
validator - Data validation
rag - Retrieval-augmented generation
hitl - Human-in-the-loop approval
fetcher - Smart HTTP fetching
transformer - Data transformation
scheduler - Task scheduling
See Starter Kit for details.
Using Agents
In Ensembles
# ensembles/enrich-company.yaml
ensemble : enrich-company
agents :
# Use custom agent
- name : enricher
agent : company-enricher
inputs :
company_name : ${input.company}
include_news : true
# Use pre-built agent
- name : validate
agent : validator
inputs :
data : ${enricher.output.company_data}
schema : company-schema
output :
data : ${enricher.output.company_data}
valid : ${validate.output.valid}
Directly via API
const result = await conductor . executeAgent ( 'company-enricher' , {
company_name: 'Anthropic' ,
include_news: true
});
Agent Composition
Agents can use other agents:
agent : full-company-profile
inputs :
company_name :
type : string
required : true
operations :
# Use company-enricher agent
- name : enrich
agent : company-enricher
inputs :
company_name : ${input.company_name}
# Use scraper agent
- name : scrape-social
agent : scraper
inputs :
url : https://linkedin.com/company/${input.company_name}
# Merge results
- name : merge
operation : code
config :
script : scripts/merge-company-profiles
input :
company_data : ${enrich.output.company_data}
social_data : ${scrape-social.output}
outputs :
profile : ${merge.output}
// scripts/merge-company-profiles.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function mergeCompanyProfiles ( context : AgentExecutionContext ) {
const { company_data , social_data } = context . input
return {
... company_data ,
social: social_data
}
}
Versioning Agents
Version agents with Edgit:
# Register agent
edgit components add company-enricher agents/company-enricher/ agent
# Create version
edgit tag create company-enricher v1.0.0
# Tag for production
edgit tag set company-enricher prod v1.0.0
edgit push --tags --force
Lock to specific versions in ensembles:
agents :
- name : enricher
agent : company-enricher@v1.0.0 # Pinned to v1.0.0
inputs :
company_name : ${input.company}
Agent Features
State Management
Share state across operations:
agent : stateful-processor
state :
schema :
processed_count : number
items : array
operations :
- name : process
operation : code
config :
script : scripts/process-item
input :
item : ${input.item}
current_items : ${state.items || []}
state :
use : [ items ]
set :
items : ${process.output.items}
processed_count : ${process.output.count}
outputs :
count : ${state.processed_count}
// scripts/process-item.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function processItem ( context : AgentExecutionContext ) {
const { item , current_items } = context . input
return {
items: [ ... current_items , item ],
count: current_items . length + 1
}
}
Caching
Cache entire agent execution:
agent : expensive-analysis
cache :
ttl : 3600
key : analysis-${input.document_id}
operations :
# ...operations...
Or cache individual operations:
operations :
- name : scrape
operation : http
config :
url : ${input.url}
cache :
ttl : 86400 # 24 hours
Error Handling
Graceful failure handling:
operations :
- name : try-primary
operation : http
config :
url : https://primary-api.com
retry :
maxAttempts : 2
- name : fallback
operation : http
condition : ${try-primary.failed}
config :
url : https://backup-api.com
outputs :
data : ${try-primary.output || fallback.output}
source : ${try-primary.executed ? 'primary' : 'fallback' }
Agent Patterns
Pattern 1: Data Pipeline
agent : data-pipeline
inputs :
raw_data : object
operations :
- name : validate
agent : validator
inputs :
data : ${input.raw_data}
- name : transform
agent : transformer
condition : ${validate.output.valid}
inputs :
data : ${input.raw_data}
- name : store
operation : storage
condition : ${validate.output.valid}
config :
type : d1
query : INSERT INTO data (json) VALUES (?)
params : [ $ { transform.output }]
outputs :
success : ${validate.output.valid}
stored : ${store.executed}
Pattern 2: AI Chain
agent : content-analyzer
inputs :
text : string
operations :
- name : extract-entities
operation : think
config :
provider : openai
model : gpt-4o-mini
prompt : Extract entities from : ${input.text}
- name : analyze-sentiment
operation : think
config :
provider : openai
model : gpt-4o-mini
prompt : Analyze sentiment : ${input.text}
- name : summarize
operation : think
config :
provider : openai
model : gpt-4o-mini
prompt : Summarize : ${input.text}
outputs :
entities : ${extract-entities.output}
sentiment : ${analyze-sentiment.output}
summary : ${summarize.output}
Pattern 3: API Orchestration
agent : multi-source-aggregator
inputs :
user_id : string
operations :
# Parallel API calls
- name : fetch-profile
operation : http
config :
url : https://api1.com/users/${input.user_id}
- name : fetch-activity
operation : http
config :
url : https://api2.com/activity?user=${input.user_id}
- name : fetch-preferences
operation : http
config :
url : https://api3.com/prefs/${input.user_id}
# Merge results
- name : merge
operation : code
config :
script : scripts/merge-user-data
input :
profile : ${fetch-profile.output.body}
activity : ${fetch-activity.output.body}
preferences : ${fetch-preferences.output.body}
outputs :
user_data : ${merge.output}
// scripts/merge-user-data.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default function mergeUserData ( context : AgentExecutionContext ) {
const { profile , activity , preferences } = context . input
return {
profile ,
activity ,
preferences
}
}
Testing Agents
// agents/company-enricher/agent.test.ts
import { describe , it , expect } from 'vitest' ;
import { TestConductor } from '@ensemble-edge/conductor/testing' ;
describe ( 'company-enricher' , () => {
it ( 'should enrich company data' , async () => {
const conductor = await TestConductor . create ();
await conductor . loadProject ( './' );
const result = await conductor . executeAgent ( 'company-enricher' , {
company_name: 'Anthropic' ,
include_news: false
});
expect ( result ). toBeSuccessful ();
expect ( result . output . company_data ). toHaveProperty ( 'name' );
expect ( result . output . company_data ). toHaveProperty ( 'industry' );
});
it ( 'should include news when requested' , async () => {
const conductor = await TestConductor . create ();
await conductor . loadProject ( './' );
const result = await conductor . executeAgent ( 'company-enricher' , {
company_name: 'Anthropic' ,
include_news: true
});
expect ( result . output . news ). toBeDefined ();
});
});
Using Agents in TypeScript Ensembles
Agents defined in YAML can be used seamlessly in TypeScript ensembles:
import { createEnsemble , step , parallel } from '@anthropic/conductor'
const enrichAndAnalyze = createEnsemble ( 'enrich-and-analyze' )
// Use custom YAML agent
. addStep (
step ( 'enricher' )
. agent ( 'company-enricher' ) // References agents/company-enricher/agent.yaml
. input ({
company_name: '${input.company}' ,
include_news: true
})
)
// Use pre-built agent
. addStep (
step ( 'validate' )
. agent ( 'validator' )
. input ({
data: '${enricher.output.company_data}' ,
schema: 'company-schema'
})
)
// Parallel agent execution
. addStep (
parallel ( 'multi-scrape' )
. steps (
step ( 'scrape-linkedin' ). agent ( 'scraper' ). input ({ url: '${input.linkedinUrl}' }),
step ( 'scrape-twitter' ). agent ( 'scraper' ). input ({ url: '${input.twitterUrl}' })
)
)
. build ()
export default enrichAndAnalyze
Agent Composition Patterns
Compose multiple agents into reusable patterns:
import { createEnsemble , step , tryStep } from '@anthropic/conductor'
// Resilient data fetching pattern
const resilientFetch = createEnsemble ( 'resilient-fetch' )
. addStep (
tryStep ( 'fetch-with-fallback' )
. try (
step ( 'primary' )
. agent ( 'fetcher' )
. input ({ url: '${input.primaryUrl}' })
. retry ({ maxAttempts: 2 , backoff: 'exponential' })
)
. catch (
step ( 'fallback' )
. agent ( 'fetcher' )
. input ({ url: '${input.fallbackUrl}' })
)
)
. build ()
export default resilientFetch
Best Practices
Single Responsibility - Each agent does one thing well
Clear Inputs/Outputs - Document what goes in and comes out
Version Agents - Use Edgit for version control
Test Thoroughly - Unit test each agent
Cache Aggressively - Cache expensive operations
Handle Failures - Always have fallbacks
Keep It Declarative - Let Conductor handle orchestration
Monitor Performance - Track execution times
Next Steps
TypeScript API TypeScript API reference
Ensembles Orchestrate agents
Starter Kit Use ready-made agents
Creating Agents Build custom agents