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.
Incorporate human feedback and approval into agent workflows for validation, guidance, and decision-making.
Human-in-the-loop enables:
static HumanInTheLoop.HumanInTheLoopBuilder humanInTheLoopBuilder();Quick Start:
HumanInTheLoop hitl = AgenticServices.humanInTheLoopBuilder()
.description("Review the generated report")
.requestWriter(request -> System.out.println("Review: " + request))
.responseReader(() -> new Scanner(System.in).nextLine())
.build();
// Use in workflow
UntypedAgent workflow = AgenticServices.sequenceBuilder()
.subAgents(generatorAgent, hitl, publisherAgent)
.build();interface HumanInTheLoopBuilder {
HumanInTheLoopBuilder description(String description);
HumanInTheLoopBuilder outputKey(String outputKey);
HumanInTheLoopBuilder async(boolean async);
HumanInTheLoopBuilder inputKey(String inputKey);
HumanInTheLoopBuilder requestWriter(Consumer<String> requestWriter);
HumanInTheLoopBuilder responseReader(Supplier<String> responseReader);
HumanInTheLoop build();
}Present information to human for review:
// Simple console output
.requestWriter(request -> System.out.println("Review: " + request))
// Formatted output
.requestWriter(request -> {
System.out.println("\n" + "=".repeat(50));
System.out.println("HUMAN REVIEW REQUIRED");
System.out.println("=".repeat(50));
System.out.println(request);
System.out.println("=".repeat(50) + "\n");
})
// File output
.requestWriter(request -> {
String filename = "review_" + System.currentTimeMillis() + ".txt";
Files.writeString(Path.of(filename), request);
System.out.println("Review request: " + filename);
})
// Slack notification
.requestWriter(request -> {
slackClient.postMessage("#approvals", "Review Required:\n" + request);
})Get input from human:
// Console input
.responseReader(() -> new Scanner(System.in).nextLine())
// Multi-line input
.responseReader(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter response (type 'END' to finish):");
StringBuilder response = new StringBuilder();
String line;
while (!(line = scanner.nextLine()).equals("END")) {
response.append(line).append("\n");
}
return response.toString();
})
// GUI dialog
.responseReader(() -> {
return JOptionPane.showInputDialog("Enter your response:");
})
// Database polling
.responseReader(() -> {
while (true) {
String response = database.checkForResponse();
if (response != null) return response;
Thread.sleep(5000);
}
})HumanInTheLoop hitl = AgenticServices.humanInTheLoopBuilder()
.description("Review generated article")
.inputKey("generated_article") // Read this from scope
.outputKey("reviewed_article") // Store response here
.requestWriter(System.out::println)
.responseReader(() -> new Scanner(System.in).nextLine())
.build();// Synchronous (blocks until response)
.async(false)
// Asynchronous (doesn't block workflow)
.async(true)
.requestWriter(request -> {
emailService.send("reviewer@company.com", "Review", request);
})
.responseReader(() -> {
return emailService.waitForReply("reviewer@company.com");
})HumanInTheLoop approval = AgenticServices.humanInTheLoopBuilder()
.description("Approve to continue")
.inputKey("action_plan")
.outputKey("approved")
.requestWriter(plan -> {
System.out.println("Action Plan:\n" + plan);
System.out.print("Approve? (yes/no): ");
})
.responseReader(() -> new Scanner(System.in).nextLine())
.build();
UntypedAgent workflow = AgenticServices.sequenceBuilder()
.subAgents(
planningAgent,
approval,
AgenticServices.conditionalBuilder()
.subAgent(
scope -> scope.readState("approved").equals("yes"),
executionAgent
)
.build()
)
.build();HumanInTheLoop reviewEdit = AgenticServices.humanInTheLoopBuilder()
.description("Review and edit the draft")
.inputKey("draft")
.outputKey("final_version")
.requestWriter(draft -> {
System.out.println("Draft:\n" + draft);
System.out.println("\nEnter final version (or press Enter to keep):");
})
.responseReader(() -> {
String edit = new Scanner(System.in).nextLine();
return edit.isEmpty() ? null : edit;
})
.build();
UntypedAgent workflow = AgenticServices.sequenceBuilder()
.subAgents(draftGenerator, reviewEdit)
.output(scope -> {
String finalVersion = (String) scope.readState("final_version");
return finalVersion != null ? finalVersion : scope.readState("draft");
})
.build();// Manager approval
HumanInTheLoop managerApproval = AgenticServices.humanInTheLoopBuilder()
.description("Manager approval")
.inputKey("budget_request")
.outputKey("manager_approved")
.requestWriter(req -> emailService.send("manager@company.com", "Approval", req))
.responseReader(() -> emailService.waitForReply("manager@company.com"))
.async(true)
.build();
// Director approval
HumanInTheLoop directorApproval = AgenticServices.humanInTheLoopBuilder()
.description("Director approval")
.inputKey("budget_request")
.outputKey("director_approved")
.requestWriter(req -> emailService.send("director@company.com", "Approval", req))
.responseReader(() -> emailService.waitForReply("director@company.com"))
.async(true)
.build();
UntypedAgent approvalWorkflow = AgenticServices.sequenceBuilder()
.subAgents(
requestGenerator,
managerApproval,
AgenticServices.conditionalBuilder()
.subAgent(
scope -> scope.readState("manager_approved").equals("yes"),
directorApproval
)
.build(),
AgenticServices.conditionalBuilder()
.subAgent(
scope -> scope.readState("manager_approved").equals("yes") &&
scope.readState("director_approved").equals("yes"),
executionAgent
)
.build()
)
.build();HumanInTheLoop feedback = AgenticServices.humanInTheLoopBuilder()
.description("Provide feedback")
.inputKey("iteration_result")
.outputKey("feedback")
.requestWriter(result -> {
System.out.println("Current result:\n" + result);
System.out.print("Feedback (or 'done' to finish): ");
})
.responseReader(() -> new Scanner(System.in).nextLine())
.build();
UntypedAgent iterativeWorkflow = AgenticServices.loopBuilder()
.maxIterations(5)
.exitCondition(scope -> {
String feedback = (String) scope.readState("feedback");
return "done".equalsIgnoreCase(feedback);
})
.subAgents(
processingAgent,
feedback,
AgenticServices.conditionalBuilder()
.subAgent(
scope -> !scope.readState("feedback").equalsIgnoreCase("done"),
improvementAgent
)
.build()
)
.build();HumanInTheLoop choice = AgenticServices.humanInTheLoopBuilder()
.description("Select best option")
.inputKey("options")
.outputKey("selected_option")
.requestWriter(options -> {
System.out.println("Available options:");
String[] opts = options.split("\n");
for (int i = 0; i < opts.length; i++) {
System.out.println((i + 1) + ". " + opts[i]);
}
System.out.print("Select option (1-" + opts.length + "): ");
})
.responseReader(() -> new Scanner(System.in).nextLine())
.build();
UntypedAgent decisionWorkflow = AgenticServices.sequenceBuilder()
.subAgents(
optionGenerator,
choice,
AgenticServices.agentBuilder()
.name("executor")
.context(scope -> "Execute option: " + scope.readState("selected_option"))
.build()
)
.build();HumanInTheLoop validation = AgenticServices.humanInTheLoopBuilder()
.description("Validate output")
.inputKey("generated_output")
.outputKey("validation_status")
.requestWriter(output -> {
System.out.println("Generated output:\n" + output);
System.out.print("Valid? (yes/no): ");
})
.responseReader(() -> new Scanner(System.in).nextLine())
.build();
UntypedAgent validationWorkflow = AgenticServices.loopBuilder()
.maxIterations(3)
.exitCondition(scope -> {
String status = (String) scope.readState("validation_status");
return "yes".equalsIgnoreCase(status);
})
.subAgents(
generatorAgent,
validation,
AgenticServices.conditionalBuilder()
.subAgent(
scope -> scope.readState("validation_status").equalsIgnoreCase("no"),
refinementAgent
)
.build()
)
.build();Example:
interface ContentWorkflow {
@SequenceAgent(
name = "content-pipeline",
subAgents = {Generator.class, HumanReview.class, Publisher.class}
)
String createContent(String topic);
@HumanInTheLoopResponseSupplier
default String getHumanReview() {
Scanner scanner = new Scanner(System.in);
System.out.print("Your review: ");
return scanner.nextLine();
}
}
interface HumanReview {
@HumanInTheLoop(
name = "human-review",
description = "Review and approve content",
inputKey = "draft",
outputKey = "approved_content",
async = false
)
String review(AgenticScope scope);
}
ContentWorkflow workflow = AgenticServices.createAgenticSystem(
ContentWorkflow.class,
chatModel
);
String result = workflow.createContent("AI in Healthcare");class WebUIHumanInTheLoop {
private final WebReviewService webService;
public HumanInTheLoop build() {
return AgenticServices.humanInTheLoopBuilder()
.description("Web-based review")
.inputKey("content")
.outputKey("reviewed_content")
.async(true)
.requestWriter(request -> {
String taskId = webService.createReviewTask(request);
System.out.println("Review task created: " + taskId);
})
.responseReader(() -> {
return webService.waitForReviewCompletion();
})
.build();
}
}HumanInTheLoop emailApproval = AgenticServices.humanInTheLoopBuilder()
.description("Email-based approval")
.inputKey("proposal")
.outputKey("decision")
.async(true)
.requestWriter(proposal -> {
emailService.send(
"approver@company.com",
"Approval Required",
"Proposal:\n" + proposal + "\n\nReply with APPROVE or REJECT"
);
})
.responseReader(() -> {
// Poll inbox for response
while (true) {
String response = emailService.checkInbox("approver@company.com");
if (response != null &&
(response.contains("APPROVE") || response.contains("REJECT"))) {
return response.contains("APPROVE") ? "approved" : "rejected";
}
Thread.sleep(30000); // Check every 30 seconds
}
})
.build();HumanInTheLoop slackApproval = AgenticServices.humanInTheLoopBuilder()
.description("Slack approval")
.inputKey("changes")
.outputKey("approval_result")
.requestWriter(changes -> {
slackClient.postMessage(
"#approvals",
"Approval needed:\n" + changes + "\n\nReact with :white_check_mark: or :x:"
);
})
.responseReader(() -> {
return slackClient.waitForReaction("#approvals");
})
.build();Install with Tessl CLI
npx tessl i tessl/maven-dev-langchain4j--langchain4j-agentic@1.11.0docs
declarative
A2AClientAgent
ActivationCondition
Agent
ConditionalAgent
ErrorHandler
ExitCondition
HumanInTheLoop
HumanInTheLoopResponseSupplier
LoopAgent
LoopCounter
Output
ParallelAgent
ParallelExecutor
PlannerAgent
SequenceAgent
SupervisorAgent
SupervisorRequest
quick-start
workflows