Error Handling
AgentEnsemble uses a hierarchy of unchecked exceptions. All exceptions extend AgentEnsembleException, making it easy to catch any framework exception with a single catch block or handle specific cases individually.
Exception Hierarchy
Section titled “Exception Hierarchy”AgentEnsembleException (base) ValidationException -- invalid configuration at build/run time TaskExecutionException -- a task failed during execution AgentExecutionException -- an LLM call failed MaxIterationsExceededException -- agent exceeded its tool-call limit PromptTemplateException -- unresolved template variables ToolExecutionException -- a tool call failed ConstraintViolationException -- required workers were not called (hierarchical workflow)ValidationException
Section titled “ValidationException”Thrown when the ensemble or its components are configured incorrectly. This exception is thrown before any LLM calls.
Common causes:
- Missing required fields (
role,goal,llm,description,expectedOutput,agent) - A task references an agent not registered with the ensemble
- Context tasks appear after the tasks that depend on them (sequential workflow)
- Circular context dependencies
maxIterations,managerMaxIterations, ormaxDelegationDepthset to zero or negativeEnsembleMemorybuilt with no memory type enabled
try { EnsembleOutput output = ensemble.run();} catch (ValidationException e) { System.err.println("Configuration error: " + e.getMessage());}TaskExecutionException
Section titled “TaskExecutionException”Thrown when a task fails during execution. Contains:
- The description of the failed task
- The role of the agent assigned to it
- A list of
TaskOutputobjects for tasks that completed before the failure
try { EnsembleOutput output = ensemble.run();} catch (TaskExecutionException e) { System.err.println("Task failed: " + e.getTaskDescription()); System.err.println("Agent: " + e.getAgentRole()); System.err.println("Cause: " + e.getCause().getMessage());
// Access results from tasks that completed before the failure for (TaskOutput completed : e.getCompletedTaskOutputs()) { System.out.println("Completed: " + completed.getTaskDescription()); System.out.println("Output: " + completed.getRaw()); }}AgentExecutionException
Section titled “AgentExecutionException”Thrown when the LLM call itself fails (network error, API error, timeout). Contains the agent role and task description.
catch (AgentExecutionException e) { System.err.println("Agent '" + e.getAgentRole() + "' failed on task '" + e.getTaskDescription() + "': " + e.getMessage());}MaxIterationsExceededException
Section titled “MaxIterationsExceededException”Thrown when an agent exceeds its maxIterations limit. Contains the configured limit and the actual count.
catch (MaxIterationsExceededException e) { System.err.println("Agent '" + e.getAgentRole() + "' exceeded its iteration limit."); System.err.println("Configured limit: " + e.getMaxIterations()); System.err.println("Actual iterations: " + e.getActualIterations());}To prevent this, either increase maxIterations on the agent or give the agent fewer, more focused tools.
PromptTemplateException
Section titled “PromptTemplateException”Thrown when a task description or expected output contains {variable} placeholders that were not resolved because the corresponding key was not in the inputs map.
catch (PromptTemplateException e) { System.err.println("Missing template variables: " + e.getMissingVariables()); // e.g., "Missing template variables: [topic, year]"}ConstraintViolationException
Section titled “ConstraintViolationException”Thrown after a hierarchical workflow manager completes when one or more workers listed in HierarchicalConstraints were never called during the run. This signals that the manager did not satisfy the required delegation constraints before finishing. Contains:
- A list of violation descriptions via
getViolations() - A list of
TaskOutputobjects for tasks that did complete viagetCompletedTaskOutputs()
try { EnsembleOutput output = ensemble.run(inputs);} catch (ConstraintViolationException e) { System.err.println("Constraint violations detected:"); for (String violation : e.getViolations()) { System.err.println(" - " + violation); }
// Partial results are still available for tasks that completed for (TaskOutput completed : e.getCompletedTaskOutputs()) { System.out.println("Completed: " + completed.getTaskDescription()); System.out.println("Output: " + completed.getRaw()); }}ToolExecutionException
Section titled “ToolExecutionException”Thrown when a tool fails to execute. This is typically wrapped inside an AgentExecutionException.
Catching All Framework Exceptions
Section titled “Catching All Framework Exceptions”try { EnsembleOutput output = ensemble.run(inputs);} catch (AgentEnsembleException e) { log.error("Ensemble failed: {}", e.getMessage(), e);}Exit Reasons
Section titled “Exit Reasons”EnsembleOutput.getExitReason() (and the convenience method isComplete()) describe how the pipeline terminated:
ExitReason | Meaning |
|---|---|
COMPLETED | All tasks ran to completion normally. isComplete() returns true. |
USER_EXIT_EARLY | A human reviewer explicitly chose to stop the pipeline at a review gate, or a HumanInputTool returned ExitEarly. |
TIMEOUT | A review gate timeout expired and the configured onTimeout action was EXIT_EARLY. Distinct from a user-initiated exit. |
ERROR | An unrecoverable exception terminated the pipeline. Partial results are available in the TaskExecutionException. |
EnsembleOutput output = ensemble.run();
switch (output.getExitReason()) { case COMPLETED: System.out.println("All done: " + output.getRaw()); break; case USER_EXIT_EARLY: System.out.println("User stopped the pipeline after " + output.completedTasks().size() + " task(s)"); System.out.println("Last output: " + output.getRaw()); break; case TIMEOUT: System.out.println("Review gate timed out after " + output.completedTasks().size() + " task(s)"); break; case ERROR: // This case is typically handled via exception -- see TaskExecutionException break;}Partial Results
Section titled “Partial Results”When the pipeline stops early — whether due to a user exit-early, a timeout, or an error —
the tasks that completed before the stop are always preserved in the EnsembleOutput.
Checking completion
Section titled “Checking completion”EnsembleOutput output = ensemble.run();if (!output.isComplete()) { System.out.println("Pipeline stopped early: " + output.getExitReason()); System.out.println("Completed: " + output.completedTasks().size() + " task(s)");}Accessing completed task outputs
Section titled “Accessing completed task outputs”// All completed task outputs in execution orderList<TaskOutput> done = output.completedTasks();
// The last completed outputoutput.lastCompletedOutput().ifPresent(o -> System.out.println("Last: " + o.getRaw()));
// A specific task's output (identity-based lookup)output.getOutput(researchTask).ifPresent(o -> System.out.println("Research: " + o.getRaw()));getOutput(Task task) uses object identity for the lookup: pass the same Task instance that
was given to Ensemble.builder().task(...). If the task completed, its output is returned; if it
was skipped or never started, Optional.empty() is returned.
Partial results on exception
Section titled “Partial results on exception”When a TaskExecutionException is thrown, the partial results from successfully completed tasks are available via e.getCompletedTaskOutputs(). This allows you to save or display intermediate work even when the ensemble fails partway through.
try { EnsembleOutput output = ensemble.run(inputs); saveResults(output);} catch (TaskExecutionException e) { // Save whatever was completed before the failure for (TaskOutput partial : e.getCompletedTaskOutputs()) { savePartialResult(partial); } // Alert on the failure alertOnFailure(e.getTaskDescription(), e.getAgentRole());}Retry Patterns
Section titled “Retry Patterns”AgentEnsemble does not have built-in retry logic. For transient failures (e.g., API rate limits), implement retry at the call site:
int attempts = 0;EnsembleOutput output = null;
while (attempts < 3) { try { output = ensemble.run(inputs); break; } catch (AgentExecutionException e) { attempts++; if (attempts == 3) throw e; Thread.sleep(1000L * attempts); // exponential back-off }}For production use, consider integrating a resilience library such as Resilience4j.