LangChain4j Agentic Framework provides a comprehensive Java library for building multi-agent AI systems with support for workflow orchestration, supervisor agents, planning-based execution, declarative configuration, agent-to-agent communication, and human-in-the-loop workflows.
Execute agents iteratively with configurable exit conditions, iteration limits, and loop counter tracking.
Loop workflows enable:
static LoopAgentService<UntypedAgent> loopBuilder();
static <T> LoopAgentService<T> loopBuilder(Class<T> agentServiceClass);Quick Start:
UntypedAgent loop = AgenticServices.loopBuilder()
.subAgents(refinementAgent)
.maxIterations(10)
.exitCondition(scope -> (Double) scope.readState("quality") >= 0.95)
.build();LoopAgentService<T> maxIterations(int maxIterations);Required setting that defines the upper limit for loop execution.
LoopAgentService<T> exitCondition(Predicate<AgenticScope> exitCondition);
LoopAgentService<T> exitCondition(BiPredicate<AgenticScope, Integer> exitCondition);
LoopAgentService<T> exitCondition(String description, Predicate<AgenticScope> exitCondition);
LoopAgentService<T> exitCondition(String description, BiPredicate<AgenticScope, Integer> exitCondition);
LoopAgentService<T> testExitAtLoopEnd(boolean checkAtEnd);Simple predicate:
.exitCondition(scope -> (Boolean) scope.readState("is_valid"))With iteration count:
.exitCondition((scope, iteration) -> {
double quality = (Double) scope.readState("quality_score", 0.0);
return quality >= 0.95 || iteration >= 8;
})Test at loop end (do-while style):
.exitCondition(scope -> (Boolean) scope.readState("is_complete"))
.testExitAtLoopEnd(true) // Test after running subagentsLoopAgentService<T> loopCounterName(String counterName);Default counter name is "loopCounter". Counter starts at 1 and increments after each iteration.
.loopCounterName("attempt_number")
.beforeCall(scope -> scope.writeState("max_attempts", 10))
.exitCondition(scope -> {
Boolean success = (Boolean) scope.readState("success");
return success != null && success;
})beforeCall callback executed (if configured)trueUntypedAgent retryLoop = AgenticServices.loopBuilder()
.subAgents(apiCallAgent)
.maxIterations(5)
.loopCounterName("attempt")
.beforeCall(scope -> {
int attempt = scope.readState("attempt", 0);
if (attempt > 1) {
long backoffMs = (long) Math.pow(2, attempt - 1) * 1000;
Thread.sleep(backoffMs);
}
})
.exitCondition(scope -> scope.readState("api_response") != null)
.build();UntypedAgent refinementLoop = AgenticServices.loopBuilder()
.subAgents(critic, improver)
.maxIterations(5)
.loopCounterName("round")
.exitCondition(scope -> {
Critique critique = (Critique) scope.readState("critique");
return critique != null && critique.getScore() >= 9.0;
})
.beforeCall(scope -> {
String initial = generator.invoke("Create article").toString();
scope.writeState("content", initial);
})
.output(scope -> Map.of(
"content", scope.readState("content"),
"rounds", scope.readState("round"),
"score", ((Critique) scope.readState("critique")).getScore()
))
.build();UntypedAgent optimizer = AgenticServices.agentBuilder()
.chatModel(chatModel)
.beforeCall(scope -> {
Object current = scope.readState("current_solution");
scope.writeState("previous_solution", current);
})
.outputKey("current_solution")
.build();
UntypedAgent optimizationLoop = AgenticServices.loopBuilder()
.subAgents(optimizer)
.maxIterations(100)
.exitCondition(scope -> {
Double current = (Double) scope.readState("current_solution");
Double previous = (Double) scope.readState("previous_solution");
if (current == null || previous == null) return false;
return Math.abs(current - previous) < 0.0001;
})
.build();Example:
interface RefinementSystem {
@LoopAgent(
name = "content-refiner",
maxIterations = 10,
exitConditionMethod = "isQualityAcceptable",
loopCounterName = "refinement_iteration",
subAgents = {ContentRefiner.class}
)
String refineContent(String initialContent);
default boolean isQualityAcceptable(AgenticScope scope) {
Double quality = (Double) scope.readState("quality_score");
Integer iteration = (Integer) scope.readState("refinement_iteration");
return (quality != null && quality >= 0.9) ||
(iteration != null && iteration >= 8);
}
}UntypedAgent loop = AgenticServices.loopBuilder()
.subAgents(processorAgent)
.maxIterations(10)
.loopCounterName("retry_attempt")
.errorHandler(errorContext -> {
Throwable error = errorContext.error();
AgenticScope scope = errorContext.agenticScope();
Integer attempt = (Integer) scope.readState("retry_attempt");
if (error instanceof TimeoutException && attempt < 5) {
scope.writeState("last_error", error.getMessage());
return new ErrorRecoveryResult("retry", true);
}
return new ErrorRecoveryResult(null, false);
})
.build();Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-agenticdocs
declarative
A2AClientAgent
ActivationCondition
Agent
ConditionalAgent
ErrorHandler
ExitCondition
HumanInTheLoop
HumanInTheLoopResponseSupplier
LoopAgent
LoopCounter
Output
ParallelAgent
ParallelExecutor
PlannerAgent
SequenceAgent
SupervisorAgent
SupervisorRequest
quick-start
workflows