Template Variables
Task descriptions and expected outputs support {variable} placeholder substitution. Variables are resolved at run time from inputs configured on the builder or passed to ensemble.run(Map<String, String>).
Basic Usage
Section titled “Basic Usage”Use curly braces to define a placeholder in any task description or expected output:
Task task = Task.builder() .description("Research the latest developments in {topic} for the {audience} audience") .expectedOutput("A 400-word summary of {topic} suitable for {audience}") .agent(researcher) .build();Supply values on the builder with .input("key", "value"):
EnsembleOutput output = Ensemble.builder() .agent(researcher) .task(task) .input("topic", "quantum computing") .input("audience", "software engineers") .build() .run();The resolved description becomes:
Research the latest developments in quantum computing for the software engineers audienceMultiple Inputs
Section titled “Multiple Inputs”Chain .input() calls — they accumulate and are all applied at run time:
Ensemble.builder() .agent(analyst) .task(financialTask) .input("company", "Acme Corp") .input("quarter", "Q4") .input("year", "2025") .build() .run();To supply a whole map at once, use .inputs(Map<String, String>):
Ensemble.builder() .agent(analyst) .task(financialTask) .inputs(Map.of("company", "Acme Corp", "quarter", "Q4", "year", "2025")) .build() .run();Variables in Expected Output
Section titled “Variables in Expected Output”Template variables are resolved in both description and expectedOutput:
Task task = Task.builder() .description("Analyse {company} financial results for Q{quarter} {year}") .expectedOutput("A financial analysis report for {company} Q{quarter} {year} with three key findings") .agent(analyst) .build();No-Variable Runs
Section titled “No-Variable Runs”When no variables are needed, call run() without any inputs configured:
EnsembleOutput output = ensemble.run();Dynamic Runs: Overriding Inputs at Invocation Time
Section titled “Dynamic Runs: Overriding Inputs at Invocation Time”When you need to run the same ensemble multiple times with different variable values, keep the ensemble instance and pass values to run(Map<String, String>). Run-time values are merged with any builder inputs; run-time values win on key conflicts:
// Create tasks and ensemble onceEnsemble ensemble = Ensemble.builder() .agent(analyst).agent(advisor) .task(analysisTask).task(recommendationTask) .build();
// Invoke multiple times with different inputsensemble.run(Map.of("week", "2026-01-06"));ensemble.run(Map.of("week", "2026-01-13"));ensemble.run(Map.of("week", "2026-01-20"));Merge example — builder provides a default, run-time call overrides it:
Ensemble ensemble = Ensemble.builder() .agent(researcher) .task(task) .input("audience", "developers") // default .build();
// audience = "developers" (builder default)ensemble.run(Map.of("topic", "AI agents"));
// audience = "executives" (run-time overrides builder)ensemble.run(Map.of("topic", "AI agents", "audience", "executives"));Missing Variables
Section titled “Missing Variables”If a task description contains a placeholder that is not supplied by either the builder inputs or the run-time inputs, a PromptTemplateException is thrown before any LLM calls:
// Task has {topic} and {year} placeholders// Builder has no inputs configuredtry { ensemble.run(Map.of("topic", "AI")); // missing "year"} catch (PromptTemplateException e) { System.err.println("Missing: " + e.getMissingVariables()); // Missing: [year]}Escaping Braces
Section titled “Escaping Braces”To include a literal {variable} in the output without substitution, use double braces:
Task task = Task.builder() .description("Write a Java method that parses {{variable}} from a string. Variable name: {varName}") .expectedOutput("A Java method with parameter name {varName}") .agent(coder) .build();With input("varName", "userId"), the resolved description is:
Write a Java method that parses {variable} from a string. Variable name: userIdSharing Variables Across Tasks
Section titled “Sharing Variables Across Tasks”All tasks in the ensemble are resolved with the same inputs. Any variable defined on the builder is available in all task descriptions and expected outputs:
var researchTask = Task.builder() .description("Research {topic} in depth") .expectedOutput("A summary of {topic}") .agent(researcher) .build();
var writeTask = Task.builder() .description("Write a blog post about {topic}") .expectedOutput("A 700-word blog post about {topic}") .agent(writer) .context(List.of(researchTask)) .build();
// Both tasks receive the same variableEnsemble.builder() .agent(researcher).agent(writer) .task(researchTask).task(writeTask) .input("topic", "AI agents") .build() .run();Variable Naming
Section titled “Variable Naming”Variable names are case-sensitive and can contain letters, digits, and underscores only (no hyphens or spaces):
{topic} -- valid{company_name} -- valid{year2025} -- valid{TOPIC} -- valid, but different from {topic}{company-name} -- invalid (hyphen not allowed){company name} -- invalid (space not allowed)Null and Empty Values
Section titled “Null and Empty Values”Values are always strings. Passing null as a value is not permitted by Map.of(). Empty strings are allowed but will produce empty substitutions:
Ensemble.builder() .agent(researcher) .task(task) .input("topic", "") // substitutes empty string .build() .run();