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.
Define and version reusable HTML templates as components for consistent UI across ensembles.
Overview
Template components enable you to:
Reuse templates across multiple HTML and Page operations
Version templates with semantic versioning for consistency
A/B test different template versions
Organize layouts and components separately
Deploy templates independently from code
Cache at the edge for fast loading (~0.1ms)
Quick Start
1. Create a Template Component
Create an HTML template file with your preferred template engine:
<!-- templates/layouts/main.html -->
<! DOCTYPE html >
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > {{title}} </ title >
{{#if description}}
< meta name = "description" content = "{{description}}" >
{{/if}}
</ head >
< body >
< header >
< h1 > {{title}} </ h1 >
</ header >
< main >
{{{content}}}
</ main >
< footer >
< p > © {{year}} {{companyName}} </ p >
</ footer >
</ body >
</ html >
2. Add to Edgit
edgit components add main-layout templates/layouts/main.html template
edgit tag create main-layout v1.0.0
edgit tag set main-layout production v1.0.0
edgit push --tags --force
3. Reference in Your Ensemble
ensemble : company-page
agents :
- name : render
operation : html
config :
template :
inline : |
<h1>{{heading}}</h1>
<p>{{content}}</p>
layout : "template://main-layout@v1.0.0"
engine : handlebars
data :
title : "Company Page"
description : "Our company information"
heading : "Welcome"
content : "This is our company page"
year : 2025
companyName : "Acme Corp"
outputs :
html : ${render.output}
All template components use the standardized URI format:
template://{path}[@{version}]
Format breakdown:
template:// - Protocol identifier for template components
{path} - Logical path to the template (e.g., layouts/main, components/header)
[@{version}] - Optional version identifier (defaults to @latest)
Version format :
@latest - Always uses the most recent version
@v1 - Uses latest patch of major version (v1.x.x)
@v1.0.0 - Specific semantic version (immutable)
@prod - Custom tag for production versions
@staging - Custom tag for staging versions
Example URIs
# Always latest version
layout : "template://layouts/main"
layout : "template://layouts/main@latest"
# Specific semantic version
layout : "template://layouts/main@v1.0.0"
layout : "template://layouts/main@v2.1.3"
# Major/minor version (gets latest patch)
layout : "template://layouts/main@v1"
layout : "template://layouts/main@v1.2"
# Custom tags
layout : "template://layouts/main@prod"
layout : "template://layouts/main@staging"
# Nested paths
layout : "template://layouts/dashboard/admin@v1"
Template Engines
Templates support multiple rendering engines:
Handlebars (Default)
Full featured with helpers, partials, and blocks:
<!-- templates/components/card.html -->
< div class = "card" >
{{#if image}}
< img src = "{{image}}" alt = "{{imageAlt}}" >
{{/if}}
< h3 > {{title}} </ h3 >
< p > {{description}} </ p >
{{#if link}}
< a href = "{{link}}" > {{linkText}} </ a >
{{/if}}
</ div >
Liquid
Django/Jekyll-style templates:
<!-- templates/layouts/blog.html -->
<! DOCTYPE html >
< html >
< head >
< title > {{ title }} </ title >
</ head >
< body >
{% if featured %}
< div class = "featured" > {{ featured }} </ div >
{% endif %}
{{ content }}
{% for post in posts %}
< article >
< h2 > {{ post.title }} </ h2 >
< p > {{ post.excerpt }} </ p >
</ article >
{% endfor %}
</ body >
</ html >
Simple
Lightweight variable substitution:
<!-- templates/emails/notification.html -->
< p > Hello {{name}}, </ p >
< p > You have {{count}} new notifications: </ p >
< ul >
{{notifications}}
</ ul >
MJML
Email templates with responsive design:
<!-- templates/emails/welcome.mjml -->
< mjml >
< mj-body >
< mj-section >
< mj-column >
< mj-text font-size = "20px" color = "#626262" >
Welcome {{name}}!
</ mj-text >
< mj-button href = "{{confirmUrl}}" >
Confirm Your Email
</ mj-button >
</ mj-column >
</ mj-section >
</ mj-body >
</ mjml >
How to Reference in Ensembles
There are three ways to reference templates in your ensembles:
Use the template:// URI format to reference versioned template components as layouts or partials:
ensemble : landing-page
agents :
- name : render
operation : html
config :
template :
inline : |
<section class="hero">
<h2>{{headline}}</h2>
<p>{{tagline}}</p>
</section>
layout : "template://layouts/main@v1.0.0"
engine : handlebars
data :
title : "Product Launch"
headline : "Revolutionary New Product"
tagline : "Transform your workflow"
outputs :
html : ${render.output}
Use {{> template://path@version}} to embed template partials:
ensemble : email-campaign
agents :
- name : render-email
operation : html
config :
template :
inline : |
{{> template://components/email-header@v1}}
<p>Hello {{name}},</p>
<p>{{message}}</p>
{{> template://components/email-footer@v1}}
engine : handlebars
data :
name : ${input.name}
message : ${input.message}
inputs :
name :
type : string
message :
type : string
outputs :
html : ${render-email.output}
3. Inline Template
For simple operations or during development, use inline templates directly.
Option A: Structured template with engine:
ensemble : simple-page
agents :
- name : render
operation : html
config :
template :
inline : |
<!DOCTYPE html>
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<h1>{{heading}}</h1>
<p>{{content}}</p>
</body>
</html>
engine : handlebars
data :
title : "Simple Page"
heading : "Welcome"
content : "This is a simple inline template"
outputs :
html : ${render.output}
Option B: Raw HTML with interpolation:
ensemble : static-page
agents :
- name : render
operation : html
config :
template :
inline : |
<!DOCTYPE html>
<html>
<head>
<title>Welcome</title>
</head>
<body>
<h1>Hello {{input.name}}</h1>
<p>Welcome to our site!</p>
</body>
</html>
inputs :
name :
type : string
outputs :
html : ${render.output}
Using Templates in Operations
With Partial Components
ensemble : dashboard
agents :
- name : render
operation : html
config :
template :
inline : |
{{> template://components/header}}
<main>
<h1>Dashboard</h1>
{{#each widgets}}
{{> template://components/card}}
{{/each}}
</main>
{{> template://components/footer}}
engine : handlebars
data :
title : "Dashboard"
widgets :
- title : "Sales"
description : "$1.2M this month"
- title : "Users"
description : "10,543 active"
outputs :
html : ${render.output}
HTML Operation with Template
name : admin-page
description : Admin dashboard page
trigger :
- type : http
path : /admin
methods : [ GET ]
public : false
flow :
- name : render-page
operation : html
config :
template : "template://pages/admin@v1"
data :
user : ${input.user}
stats : ${input.stats}
output :
_raw : ${render-page.output}
Email with Template
ensemble : welcome-email
agents :
- name : send-welcome
operation : email
config :
to : ${input.email}
subject : "Welcome to Acme Corp"
html :
template : "template://emails/welcome@v1"
engine : mjml
data :
name : ${input.name}
confirmUrl : ${input.confirmUrl}
inputs :
email :
type : string
name :
type : string
confirmUrl :
type : string
Layouts and Partials
Layouts
Layouts wrap content with common structure (header, footer, etc.):
agents :
- name : render
operation : html
config :
template :
inline : "<h1>{{heading}}</h1><p>{{body}}</p>"
layout : "template://layouts/main@v1"
data :
title : "Page Title"
heading : "Welcome"
body : "Page content"
The {{{content}}} variable in the layout receives the rendered template.
Partials
Partials are reusable components included in templates:
<!-- Main template -->
< div class= "page" >
{{> template : //components/header }}
< main >
{{ content }}
</ main >
{{> template : //components/footer }}
</ div >
Partials automatically receive the same data context as the parent template.
Template components are automatically cached for 1 hour (3600 seconds) after first load.
Default Caching
agents :
- name : render
operation : html
config :
layout : "template://layouts/main@v1"
# Cached for 1 hour automatically
Performance:
First load : Fetched from KV (~5-10ms)
Subsequent loads : Served from edge cache (~0.1ms)
Cache per version : Each version cached independently
Custom Cache TTL
agents :
- name : render
operation : html
config :
layout : "template://layouts/main@v1"
cache :
ttl : 86400 # 24 hours for stable layouts
Bypass Cache
agents :
- name : render
operation : html
config :
layout : "template://layouts/main@latest"
cache :
bypass : true # Fresh load every time during development
Best Practices
1. Version Your Templates
Use semantic versioning to track changes:
# First version
edgit tag create main-layout v1.0.0
# Visual improvement
edgit tag create main-layout v1.1.0
# Breaking change (structure changed)
edgit tag create main-layout v2.0.0
Create stable version tags for production ensembles:
edgit tag set main-layout production v1.2.3
# Production ensemble uses stable tag
layout : "template://layouts/main@production"
3. Organize by Purpose
Use path hierarchies for organization:
templates/
├── layouts/
│ ├── main.html # Standard page layout
│ ├── dashboard.html # Dashboard layout
│ └── email.html # Email layout
├── components/
│ ├── header.html # Site header
│ ├── footer.html # Site footer
│ ├── card.html # Content card
│ └── navigation.html # Navigation menu
└── emails/
├── welcome.mjml # Welcome email
├── notification.mjml # Notification email
└── report.html # Report email
4. Long Cache for Stable Templates
# Static layouts that rarely change
layout : "template://layouts/main@v1"
cache :
ttl : 86400 # 24 hours
Add comments to templates for documentation:
<!--
Template: Main Layout
Version: v1.0.0
Variables:
- title (required): Page title
- description (optional): Meta description
- content (required): Page content
- year (optional): Copyright year
- companyName (optional): Company name
-->
<! DOCTYPE html >
< html >
...
</ html >
ensemble : template-test
agents :
# Test new version
- name : test-new
operation : html
config :
template :
inline : "<p>Test content</p>"
layout : "template://layouts/main@v2.0.0"
data :
title : "Test"
# Compare with production
- name : test-prod
operation : html
config :
template :
inline : "<p>Test content</p>"
layout : "template://layouts/main@production"
data :
title : "Test"
Component Catalog
Conductor includes a catalog of production-ready templates:
conductor/catalog/components/templates/
├── layouts/
│ └── main.html # Full page layout
└── components/
├── header.html # Site header with navigation
├── footer.html # Site footer with links
├── card.html # Content card component
└── navigation.html # Navigation menu
Deploy Catalog Templates
# Deploy header component
edgit components add header conductor/catalog/components/templates/components/header.html template
edgit tag create header v1.0.0
edgit tag set header production v1.0.0
# Deploy main layout
edgit components add main-layout conductor/catalog/components/templates/layouts/main.html template
edgit tag create main-layout v1.0.0
edgit tag set main-layout production v1.0.0
# Push tags
edgit push --tags --force
See the catalog README for complete documentation.
Versioning Strategy
Development Workflow
# 1. Create new version
edgit tag create main-layout v1.1.0
# 2. Test with staging ensemble
ensemble: page-staging
agents:
- name: render
operation: html
config:
layout: "template://layouts/main@v1.1.0"
# 3. Promote to production
edgit tag set main-layout production v1.1.0
Rollback Strategy
# If v2.0.0 has issues, keep using v1.0.0
ensemble : landing-page-stable
agents :
- name : render
operation : html
config :
layout : "template://layouts/main@v1.0.0"
Using ctx API in Agents
When building custom agents with TypeScript handlers, you can access and render templates through the ctx API:
ctx.templates.render(name, vars)
Render an HTML template with variables:
// agents/email-generator/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
interface EmailInput {
name : string
confirmUrl : string
}
export default async function generateEmail ( ctx : AgentExecutionContext ) {
const { name , confirmUrl } = ctx . input as EmailInput
// Render template with variables
const html = await ctx . templates . render ( 'emails/welcome' , {
name ,
confirmUrl ,
year: new Date (). getFullYear (),
companyName: 'Acme Corp'
})
return {
html ,
subject: 'Welcome to Acme Corp' ,
to: ctx . input . email
}
}
Dynamic Template Selection
// agents/dynamic-renderer/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function renderPage ( ctx : AgentExecutionContext ) {
const { templateType , data } = ctx . input as {
templateType : 'basic' | 'premium' | 'enterprise'
data : Record < string , any >
}
// Select template based on type
const templateName = `pages/ ${ templateType } `
const html = await ctx . templates . render ( templateName , {
... data ,
timestamp: new Date (). toISOString ()
})
return {
html ,
type: templateType
}
}
Rendering with Layout
// agents/page-generator/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function generatePage ( ctx : AgentExecutionContext ) {
const { title , content } = ctx . input as {
title : string
content : string
}
// Render content template
const contentHtml = await ctx . templates . render ( 'components/content' , {
content
})
// Render with layout
const html = await ctx . templates . render ( 'layouts/main' , {
title ,
content: contentHtml ,
year: new Date (). getFullYear (),
companyName: 'Acme Corp'
})
return {
html ,
title
}
}
Multi-Template Composition
// agents/email-composer/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function composeEmail ( ctx : AgentExecutionContext ) {
const { recipient , items } = ctx . input as {
recipient : { name : string ; email : string }
items : Array <{ name : string ; price : number }>
}
// Render multiple templates
const header = await ctx . templates . render ( 'components/email-header' , {
name: recipient . name
})
const itemsHtml = await Promise . all (
items . map ( item =>
ctx . templates . render ( 'components/order-item' , item )
)
)
const footer = await ctx . templates . render ( 'components/email-footer' , {
year: new Date (). getFullYear ()
})
// Compose final email
const html = `
${ header }
<div class="items">
${ itemsHtml . join ( ' \n ' ) }
</div>
${ footer }
`
return {
html ,
to: recipient . email ,
subject: 'Your Order Confirmation'
}
}
Template Rendering with Error Handling
// agents/safe-renderer/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function safeRender ( ctx : AgentExecutionContext ) {
const { templateName , data } = ctx . input as {
templateName : string
data : Record < string , any >
}
try {
// Try to render template
const html = await ctx . templates . render ( templateName , data )
return {
success: true ,
html
}
} catch ( error ) {
// Fallback to default template
const html = await ctx . templates . render ( 'default' , {
error: 'Template not found' ,
message: 'Using default template'
})
return {
success: false ,
html ,
error: error instanceof Error ? error . message : 'Render failed'
}
}
}
Conditional Template Rendering
// agents/conditional-renderer/index.ts
import type { AgentExecutionContext } from '@ensemble-edge/conductor'
export default async function renderConditional ( ctx : AgentExecutionContext ) {
const { userTier , data } = ctx . input as {
userTier : 'free' | 'pro' | 'enterprise'
data : Record < string , any >
}
// Select template based on user tier
let templateName : string
switch ( userTier ) {
case 'enterprise' :
templateName = 'pages/enterprise-dashboard'
break
case 'pro' :
templateName = 'pages/pro-dashboard'
break
default :
templateName = 'pages/basic-dashboard'
}
const html = await ctx . templates . render ( templateName , {
... data ,
tier: userTier
})
return {
html ,
tier: userTier ,
template: templateName
}
}
Troubleshooting
Template Not Found
Error : Component not found: template://layouts/main@v1.0.0
Solution :
Check template exists: edgit list templates
Check version: edgit tag list main-layout
Verify deployment: edgit tag show main-layout@v1.0.0
Template Rendering Errors
Issue : Variables not being replaced or syntax errors
Solutions :
Check engine matches template syntax (handlebars vs liquid vs simple)
Verify all required variables are provided in data
Check template syntax is valid for the chosen engine
Test template locally before deploying
Partial Not Loading
Issue : {{> template://components/header}} not rendering
Solution : Ensure the partial is:
Deployed to KV with correct path
Using correct URI format
Compatible with the template engine (handlebars supports partials)
Cache Issues
Issue : Updated template not being used
Solution : Invalidate cache or set cache.bypass: true
agents :
- name : render
operation : html
config :
layout : "template://layouts/main@latest"
cache :
bypass : true # Force fresh load
Next Steps
HTML Operation Generate HTML with templates
PDF Operation Generate PDFs from templates
Email Operation Send emails with templates
Edgit Versioning Version control for components