Skip to content

Step 03 - Building nested agent workflows

Expanding Requirements

The Miles of Smiles management team has decided to get more serious about car maintenance. When cars are returned, the provided feedback should be analyzed — to see if car cleaning is needed and also to see if car maintenance is needed. If maintenance is needed then the car should be given to the maintenance team. If the car doesn’t need maintenance but does need cleaning then it should be given to the car wash team.

There are a number of things that we now need our car management app to handle:

  • Car returns from rentals, the car wash, or the maintenance department.
  • Analyzing the return feedback to see if a car wash and/or maintenance are required.
  • Based on the feedback, getting the maintenance department to work on the car.
  • Based on the feedback, getting the car wash team to clean the car.
  • Automatically updating the car condition based on the analysis of the feedback.

Nested Workflows

In the previous step, we used a sequence workflow, which ran the car wash agent followed by the car condition feedback agent. In this step, we will build a sequence workflow that contains a parallel workflow, a conditional workflow, and a single agent (see diagram below).

At each step in the workflow, the agentic framework checks the inputs needed by the next workflow or agent that needs to run. When the outer-most workflow starts (in this case our sequence workflow), parameters are provided by the caller of the workflow interface. In subsequent steps within the workflow, the framework gathers values for input parameters from the AgenticScope. The output from each agent or workflow is added to the AgenticScope (using the agent’s outputName setting). The output from a workflow is typically the output of the last agent in the workflow. When building the agent/workflow, you can also specify an output method, which will be run after the response from the agent/workflow is created — this is particularly useful for parallel workflows, to customize what to fill into the corresponding outputName for that agent/workflow.

What are we going to build?

App Blueprint

Starting from our app in step-02, we need to:

Create/Update agent and workflow declarations:

  • Create a MaintenanceFeedbackAgent
  • Create a CarWashFeedbackAgent
  • Create a FeebackWorkflow
  • Modify the CarProcessingWorkflow to add the maintenance feedback
  • Create a MaintenanceAgent
  • Modify the CarWashAgent to use the output from the car wash feedback agent
  • Create an ActionWorkflow
  • Modify the CarConditionFeedbackAgent to use the output from the feedback agents

Create the maintenance tool and maintenance returns API:

  • Create a MaintenanceTool
  • Modify CarManagementResource to add a maintenance returns API

Define the agents and workflows:

  • Define the MaintenanceAgent
  • Define the MaintenanceFeedbackAgent
  • Define a parallel workflow, FeebackWorkflow, including the CarWashFeedbackAgent and MaintenanceFeedbackAgent
  • Define a conditional workflow, ActionWorkflow, including the CarWashAgent and MaintenanceAgent
  • Modify the sequence workflow, to include the feedback workflow, the action workflow and the car condition feedback agent

Before You Begin

You can either use the code from step-01 and continue from there, or check the final code of the step located in the step-03 directory.

Do not forget to close the application

If you have the application running from the previous step and decide to use the step-03 directory, make sure to stop it (CTRL+C) before continuing.

If you are continuing to build the app in the step-01 directory, start by copying some files (which don’t relate to the experience of building agentic AI apps) from step-03. Run the following command from your section-2 directory:

For Linux/macOS:

./setup-step-03.sh

Create/Update agent and workflow declarations

Create a MaintenanceFeedbackAgent

Create a MaintenanceFeedbackAgent to analyze the feedback from rental returns, car wash returns and maintenance returns. The agent will decide if maintenance is required on the car.

In the system prompt, instruct the agent to include MAINTENANCE_NOT_REQUIRED in its response if no maintenance is needed so that we can easily check for that string when we build our conditional agents.

Create the file in your src/main/java/com/carmanagement/agentic/agents directory.

MaintenanceFeedbackAgent.java
package com.carmanagement.agentic.agents;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.agentic.Agent;

/**
 * Agent that analyzes feedback to determine if maintenance is needed.
 */
public interface MaintenanceFeedbackAgent {

    @SystemMessage("""
        /nothink, Reasoning: low.
        You are a car maintenance analyzer for a car rental company. Your job is to determine if a car needs maintenance based on feedback.
        Analyze the feedback and car information to decide if maintenance is needed.
        If the feedback mentions mechanical issues, strange noises, performance problems, or anything that suggests
        the car needs maintenance, recommend appropriate maintenance.
        Be specific about what type of maintenance is needed (oil change, tire rotation, brake service, engine service, transmission service).
        If no service of any kind, repairs or maintenance are needed, respond with "MAINTENANCE_NOT_REQUIRED".
        Include the reason for your choice but keep your response short.
        """)
    @UserMessage("""
        Car Information:
        Make: {{carMake}}
        Model: {{carModel}}
        Year: {{carYear}}
        Previous Condition: {{carCondition}}

        Feedback:
        Rental Feedback: {{rentalFeedback}}
        Car Wash Feedback: {{carWashFeedback}}
        Maintenance Feedback: {{maintenanceFeedback}}
        """)
    @Agent(outputName="maintenanceRequest", description="Car maintenance analyzer. Using feedback, determines if a car needs maintenance.")
    String analyzeForMaintenance(
            @V("carMake") String carMake,
            @V("carModel") String carModel,
            @V("carYear") Integer carYear,
            @V("carNumber") Integer carNumber,
            @V("carCondition") String carCondition,
            @V("rentalFeedback") String rentalFeedback,
            @V("carWashFeedback") String carWashFeedback,
            @V("maintenanceFeedback") String maintenanceFeedback);
}

Create a CarWashFeedbackAgent

Create a CarWashFeedbackAgent to analyze the feedback from rental returns, car wash returns and maintenance returns. The agent will decide if any cleaning is required of the car.

In the system prompt instruct the agent to include CARWASH_NOT_REQUIRED in its response if no cleaning is needed so that we can easily check for that string when we build our conditional agents.

Create the file in your src/main/java/com/carmanagement/agentic/agents directory.

CarWashFeedbackAgent.java
package com.carmanagement.agentic.agents;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.agentic.Agent;

/**
 * Agent that analyzes feedback to determine if a car wash is needed.
 */
public interface CarWashFeedbackAgent {

    @SystemMessage("""
        /nothink, Reasoning: low.
        You are a car wash analyzer for a car rental company. Your job is to determine if a car needs washing based on feedback.
        Analyze the feedback and car information to decide if a car wash is needed.
        If the feedback mentions dirt, mud, stains, or anything that suggests the car is dirty, recommend a car wash.
        Be specific about what type of car wash is needed (exterior, interior, detailing, waxing).
        If no interior or exterior car cleaning services are needed based on the feedback, respond with "CARWASH_NOT_REQUIRED".
        Include the reason for your choice but keep your response short.
        """)
    @UserMessage("""
        Car Information:
        Make: {{carMake}}
        Model: {{carModel}}
        Year: {{carYear}}
        Previous Condition: {{carCondition}}

        Feedback:
        Rental Feedback: {{rentalFeedback}}
        Car Wash Feedback: {{carWashFeedback}}
        Maintenance Feedback: {{maintenanceFeedback}}
        """)
    @Agent(outputName="carWashRequest", description="Car wash analyzer. Using feedback, determines if a car wash is needed.")
    String analyzeForCarWash(
            @V("carMake") String carMake,
            @V("carModel") String carModel,
            @V("carYear") Integer carYear,
            @V("carNumber") Integer carNumber,
            @V("carCondition") String carCondition,
            @V("rentalFeedback") String rentalFeedback,
            @V("carWashFeedback") String carWashFeedback,
            @V("maintenanceFeedback") String maintenanceFeedback);
}

Create a FeedbackWorkflow

We need to analyze feedback from car returns both from the perspective of car cleanliness and needed repairs/maintenance. Since those are independent considerations we can do those analyses in parallel (to improve responsiveness of the overall workflow).

Create a FeedbackWorkflow which we will use for our parallel workflow.

Create the file in your src/main/java/com/carmanagement/agentic/workflow directory.

FeedbackWorkflow.java
package com.carmanagement.agentic.workflow;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.V;

/**
 * Workflow for processing car feedback in parallel.
 */
public interface FeedbackWorkflow {

    /**
     * Runs multiple feedback agents in parallel to analyze different aspects of car feedback.
     */
    @Agent(outputName="feedbackResult")
    void analyzeFeedback(
            @V("carMake") String carMake,
            @V("carModel") String carModel,
            @V("carYear") Integer carYear,
            @V("carCondition") String carCondition,
            @V("rentalFeedback") String rentalFeedback,
            @V("carWashFeedback") String carWashFeedback,
            @V("maintenanceFeedback") String maintenanceFeedback);
}

Modify the CarProcessingWorkflow to add the maintenance feedback

The CarProcessingWorkflow represents our overall agent system. Modify the CarProcessingWorkflow to add the parameter for the feedback related to maintenance:

Update the file in your src/main/java/com/carmanagement/agentic/workflow directory.

CarProcessingWorkflow.java
package com.carmanagement.agentic.workflow;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.agentic.scope.ResultWithAgenticScope;
import dev.langchain4j.service.V;

/**
 * Workflow for processing car returns using a sequence of agents.
 */
public interface CarProcessingWorkflow {

    /**
     * Processes a car return by running feedback analysis and then appropriate actions.
     */
    @Agent(outputName="carProcessingAgentResult")
    ResultWithAgenticScope<String> processCarReturn(
            @V("carMake") String carMake,
            @V("carModel") String carModel,
            @V("carYear") Integer carYear,
            @V("carNumber") Integer carNumber,
            @V("carCondition") String carCondition,
            @V("rentalFeedback") String rentalFeedback,
            @V("carWashFeedback") String carWashFeedback,
            @V("maintenanceFeedback") String maintenanceFeedback);
}

Create a MaintenanceAgent

Create a maintenance agent that can use a maintenance tool to request maintenance. The maintenance requests will be created by the MaintenanceFeedbackAgent. The MaintenanceFeedbackAgent uses an outputName of maintenanceRequest.

Create the file in your src/main/java/com/carmanagement/agentic/agents directory.

MaintenanceAgent.java
package com.carmanagement.agentic.agents;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.agentic.Agent;

/**
 * Agent that determines what maintenance services to request.
 */
public interface MaintenanceAgent {

    @SystemMessage("""
        /nothink, Reasoning: low.
        You handle intake for the car maintenance department of a car rental company.
        It is your job to submit a request to the provided requestMaintenance function to take action on the maintenance request.
        Be specific about what services are needed based on the maintenance request.
        """)
    @UserMessage("""
        Car Information:
        Make: {{carMake}}
        Model: {{carModel}}
        Year: {{carYear}}
        Car Number: {{carNumber}}

        Maintenance Request:
        {{maintenanceRequest}}
        """)
    @Agent(outputName="maintenanceAgentResult", description="Car maintenance specialist. Using car information and request, determines what maintenance services are needed.")
    String processMaintenance(
            @V("carMake") String carMake,
            @V("carModel") String carModel,
            @V("carYear") Integer carYear,
            @V("carNumber") Integer carNumber,
            @V("maintenanceRequest") String maintenanceRequest);
}

Modify the CarWashAgent to use the output from the CarWashFeedback agent

We need to modify the CarWashAgent to rely on requests created by the CarWashFeedbackAgent. The CarWashFeedbackAgent, uses an outputName of carWashRequest. Modify the CarWashAgent to use the carWashRequest as its input.

Update the file in your src/main/java/com/carmanagement/agentic/agents directory.

CarWashAgent.java
package com.carmanagement.agentic.agents;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.agentic.Agent;

/**
 * Agent that determines what car wash services to request.
 */
public interface CarWashAgent {

    @SystemMessage("""
        /nothink, Reasoning: low.
        You handle intake for the car wash department of a car rental company.
        It is your job to submit a request to the provided requestCarWash function to take action on the request.
        Be specific about what services are needed based on the car wash request.
        If no specific car wash request is provided, request a standard exterior wash.
        """)
    @UserMessage("""
        Car Information:
        Make: {{carMake}}
        Model: {{carModel}}
        Year: {{carYear}}
        Car Number: {{carNumber}}

        Car Wash Request:
        {{carWashRequest}}
        """)
    @Agent(outputName="carWashAgentResult", description="Car wash specialist. Determines what car wash services are needed.")
    String processCarWash(
            @V("carMake") String carMake,
            @V("carModel") String carModel,
            @V("carYear") Integer carYear,
            @V("carNumber") Integer carNumber,
            @V("carWashRequest") String carWashRequest);
}

Create an ActionWorkflow

In cases where the feedback agents indicate car maintenance is required, we want to invoke the maintenance agent (to request maintenance). In cases where no maintenance is required, but a car wash is required, we want to invoke the car wash agent (to request a car wash). If the feedback indicates neither is required then we should not run either agent. For this, we will need a conditional workflow.

Create an ActionWorkflow which we will use for our conditional workflow, using the carWashRequest and maintenanceRequest as inputs.

Create the file in your src/main/java/com/carmanagement/agentic/workflow directory.

ActionWorkflow.java
package com.carmanagement.agentic.workflow;

import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.V;

/**
 * Workflow for processing car actions conditionally.
 */
public interface ActionWorkflow {

    /**
     * Runs the appropriate action agent based on the feedback analysis.
     */
    @Agent(outputName="actionResult")
    String processAction(
            @V("carMake") String carMake,
            @V("carModel") String carModel,
            @V("carYear") Integer carYear,
            @V("carNumber") Integer carNumber,
            @V("carCondition") String carCondition,
            @V("carWashRequest") String carWashRequest,
            @V("maintenanceRequest") String maintenanceRequest);

}

Modify the CarConditionFeedbackAgent to use the output from the feedback agents

Similarly to the CarWashAgent and MaintenanceAgent, we will have the CarConditionFeedbackAgent rely on the output from the feedback agents rather than interpreting the returns feedback directly itself.

Update the file in your src/main/java/com/carmanagement/agentic/agents directory.

CarConditionFeedbackAgent.java
package com.carmanagement.agentic.agents;

import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.agentic.Agent;

/**
 * Agent that analyzes feedback to update the car condition.
 */
public interface CarConditionFeedbackAgent {

    @SystemMessage("""
        /nothink, Reasoning: low.
        You are a car condition analyzer for a car rental company. Your job is to determine the current condition of a car based on feedback.
        Analyze all feedback and the previous car condition to provide an updated condition description.
        Always provide a concise condition description, even if there's minimal feedback.
        Do not add any headers or prefixes to your response.
        """)
    @UserMessage("""
        Car Information:
        Make: {{carMake}}
        Model: {{carModel}}
        Year: {{carYear}}
        Previous Condition: {{carCondition}}

        Feedback from other agents:
        Car Wash Recommendation: {{carWashRequest}}
        Maintenance Recommendation: {{maintenanceRequest}}
        """)
    @Agent(outputName="carCondition", description="Car condition analyzer. Determines the current condition of a car based on feedback.")
    String analyzeForCondition(
        @V("carMake") String carMake,
        @V("carModel") String carModel,
        @V("carYear") Integer carYear,
        @V("carNumber") Integer carNumber,
        @V("carCondition") String carCondition,
        @V("carWashRequest") String carWashRequest,
        @V("maintenanceRequest") String maintenanceRequest);
}

Create the maintenance tool and maintenance returns API

Create a MaintenanceTool

We need to create a MaintenanceTool that can be used by the MaintenanceAgent to select maintenance options to open a request for maintenance. The tool should let an agent request a variety of maintenance tasks for the car, such as oil changes, tire rotations, brake service, engine service or transmission service.

Create the file in your src/main/java/com/carmanagement/agentic/tools directory.

MaintenanceTool.java
package com.carmanagement.agentic.tools;

import com.carmanagement.model.CarInfo;
import com.carmanagement.model.CarStatus;
import com.carmanagement.service.CarService;
import dev.langchain4j.agent.tool.Tool;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;

/**
 * Tool for requesting car maintenance operations.
 */
@Dependent
public class MaintenanceTool {

    @Inject
    CarService carService;

    /**
     * Requests maintenance for a car based on the provided parameters.
     *
     * @param carNumber The car number
     * @param carMake The car make
     * @param carModel The car model
     * @param carYear The car year
     * @param oilChange Whether to request an oil change
     * @param tireRotation Whether to request tire rotation
     * @param brakeService Whether to request brake service
     * @param engineService Whether to request engine service
     * @param transmissionService Whether to request transmission service
     * @param requestText The maintenance request text
     * @return A summary of the maintenance request
     */
    @Tool("Requests maintenance with the specified options")
    public String requestMaintenance(
            Integer carNumber,
            String carMake,
            String carModel,
            Integer carYear,
            boolean oilChange,
            boolean tireRotation,
            boolean brakeService,
            boolean engineService,
            boolean transmissionService,
            String requestText) {

        // In a real implementation, this would make an API call to a maintenance service
        // or update a database with the maintenance request

        // Update car status to IN_MAINTENANCE
        CarInfo carInfo = carService.getCarById(carNumber);
        if (carInfo != null) {
            carInfo.setStatus(CarStatus.IN_MAINTENANCE);
        }

        StringBuilder summary = new StringBuilder();
        summary.append("Maintenance requested for ").append(carMake).append(" ")
               .append(carModel).append(" (").append(carYear).append("), Car #")
               .append(carNumber).append(":\n");

        if (oilChange) {
            summary.append("- Oil change\n");
        }

        if (tireRotation) {
            summary.append("- Tire rotation\n");
        }

        if (brakeService) {
            summary.append("- Brake service\n");
        }

        if (engineService) {
            summary.append("- Engine service\n");
        }

        if (transmissionService) {
            summary.append("- Transmission service\n");
        }

        if (requestText != null && !requestText.isEmpty()) {
            summary.append("Additional notes: ").append(requestText);
        }

        String result = summary.toString();
        System.out.println("MaintenanceTool result: " + result);
        return result;
    }
}

Modify CarManagementResource to add a maintenance returns API

We’ll modify the CarManagementResource to add a maintenance returns API. This will be called by the UI and will be very similar to the car wash returns API.

Update the file in your src/main/java/com/carmanagement/resource directory.

CarManagementResource.java
package com.carmanagement.resource;

import com.carmanagement.service.CarManagementService;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

/**
 * REST resource for car management operations.
 */
@Path("/car-management")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CarManagementResource {

    @Inject
    CarManagementService carManagementService;

    /**
     * Process a car return from rental.
     * 
     * @param carNumber The car number
     * @param rentalFeedback Optional rental feedback
     * @return Result of the processing
     */
    @POST
    @Path("/rental-return/{carNumber}")
    public Response processRentalReturn(
            @PathParam("carNumber") Integer carNumber,
            @QueryParam("rentalFeedback") String rentalFeedback) {

        try {
            String result = carManagementService.processCarReturn(carNumber, rentalFeedback, "", "");
            return Response.ok(result).build();
        } catch (Exception e) {
            e.printStackTrace();
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity("Error processing rental return: " + e.getMessage())
                    .build();
        }
    }

    /**
     * Process a car return from car wash.
     * 
     * @param carNumber The car number
     * @param carWashFeedback Optional car wash feedback
     * @return Result of the processing
     */
    @POST
    @Path("/car-wash-return/{carNumber}")
    public Response processCarWashReturn(
            @PathParam("carNumber") Integer carNumber,
            @QueryParam("carWashFeedback") String carWashFeedback) {

        try {
            String result = carManagementService.processCarReturn(carNumber, "", carWashFeedback, "");
            return Response.ok(result).build();
        } catch (Exception e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity("Error processing car wash return: " + e.getMessage())
                    .build();
        }
    }

    /**
     * Process a car return from maintenance.
     *
     * @param carNumber The car number
     * @param maintenanceFeedback Optional maintenance feedback
     * @return Result of the processing
     */
    @POST
    @Path("/maintenance-return/{carNumber}")
    public Response processMaintenanceReturn(
            @PathParam("carNumber") Integer carNumber,
            @QueryParam("maintenanceFeedback") String maintenanceFeedback) {

        try {
            String result = carManagementService.processCarReturn(carNumber, "", "", maintenanceFeedback);
            return Response.ok(result).build();
        } catch (Exception e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity("Error processing maintenance return: " + e.getMessage())
                    .build();
        }
    }
}

Define the agents and workflows

We’ll need to make a few changes to our CarManagementService to define new workflows and update existing workflows:

Update the file in your src/main/java/com/carmanagement/service directory.

CarManagementService.java
package com.carmanagement.service;

import com.carmanagement.agentic.agents.CarConditionFeedbackAgent;
import com.carmanagement.agentic.agents.CarWashAgent;
import com.carmanagement.agentic.agents.CarWashFeedbackAgent;
import com.carmanagement.agentic.agents.MaintenanceAgent;
import com.carmanagement.agentic.agents.MaintenanceFeedbackAgent;
import com.carmanagement.agentic.config.Models;
import com.carmanagement.agentic.tools.CarWashTool;
import com.carmanagement.agentic.tools.MaintenanceTool;
import com.carmanagement.agentic.workflow.ActionWorkflow;
import com.carmanagement.agentic.workflow.CarProcessingWorkflow;
import com.carmanagement.agentic.workflow.FeedbackWorkflow;
import com.carmanagement.model.CarInfo;
import com.carmanagement.model.CarStatus;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.scope.AgenticScope;
import dev.langchain4j.agentic.scope.ResultWithAgenticScope;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.annotation.PostConstruct;

/**
 * Service for managing car returns from various operations.
 */
@ApplicationScoped
public class CarManagementService {

    /**
     * Enum representing the type of agent to be selected for car processing
     */
    public enum AgentType {
        MAINTENANCE,
        CAR_WASH,
        NONE
    }

    @Inject
    CarService carService;

    @Inject
    Models models;

    @Inject
    CarWashTool carWashTool;

    @Inject
    MaintenanceTool maintenanceTool;

    private CarProcessingWorkflow carProcessingWorkflow;

    @PostConstruct
    void initialize() {
        carProcessingWorkflow = createCarProcessingWorkflow();
    }

    private CarProcessingWorkflow createCarProcessingWorkflow() {
        // CarWashAgent
        CarWashAgent carWashAgent = AgenticServices
                .agentBuilder(CarWashAgent.class)
                .chatModel(models.baseModel())
                .tools(carWashTool)
                .build();

        // MaintenanceAgent (1)
        MaintenanceAgent maintenanceAgent = AgenticServices
                .agentBuilder(MaintenanceAgent.class)
                .chatModel(models.baseModel())
                .tools(maintenanceTool)
                .build();


        // CarWashFeedbackAgent
        CarWashFeedbackAgent carWashFeedbackAgent = AgenticServices
                .agentBuilder(CarWashFeedbackAgent.class)
                .chatModel(models.baseModel())
                .build();


        // MaintenanceFeedbackAgent (2)
        MaintenanceFeedbackAgent maintenanceFeedbackAgent = AgenticServices
                .agentBuilder(MaintenanceFeedbackAgent.class)
                .chatModel(models.baseModel())
                .build();

        // CarConditionFeedbackAgent
        CarConditionFeedbackAgent carConditionFeedbackAgent = AgenticServices
                .agentBuilder(CarConditionFeedbackAgent.class)
                .chatModel(models.baseModel())
                .build();


        // FeedbackWorkflow (3)
        FeedbackWorkflow feedbackWorkflow = AgenticServices
                .parallelBuilder(FeedbackWorkflow.class)
                .subAgents(carWashFeedbackAgent, maintenanceFeedbackAgent)
                .build();

        // ActionWorkflow (4)
        ActionWorkflow actionWorkflow = AgenticServices
                .conditionalBuilder(ActionWorkflow.class)
                .subAgents(
                    // Check if maintenance is required
                    agenticScope -> selectAgent(agenticScope) == AgentType.MAINTENANCE,
                    maintenanceAgent
                )
                .subAgents(
                    // Check if car wash is required
                    agenticScope -> selectAgent(agenticScope) == AgentType.CAR_WASH,
                    carWashAgent
                )
                .build();

        // CarProcessingWorkflow (5)
        CarProcessingWorkflow carProcessingWorkflow = AgenticServices
                .sequenceBuilder(CarProcessingWorkflow.class)
                .subAgents(feedbackWorkflow, actionWorkflow, carConditionFeedbackAgent)
                .build();

        return carProcessingWorkflow;
    }

    /**
     * Process a car return from any operation.
     *
     * @param carNumber The car number
     * @param rentalFeedback Optional rental feedback
     * @param carWashFeedback Optional car wash feedback
     * @param maintenanceFeedback Optional maintenance feedback
     * @return Result of the processing
     */
    public String processCarReturn(Integer carNumber, String rentalFeedback, String carWashFeedback, String maintenanceFeedback) {
        CarInfo carInfo = carService.getCarById(carNumber);
        if (carInfo == null) {
            return "Car not found with number: " + carNumber;
        }

        // Process the car return using the workflow and get the AgenticScope
        ResultWithAgenticScope<String> resultWithScope = carProcessingWorkflow.processCarReturn(
                carInfo.getMake(),
                carInfo.getModel(),
                carInfo.getYear(),
                carNumber,
                carInfo.getCondition(),
                rentalFeedback != null ? rentalFeedback : "",
                carWashFeedback != null ? carWashFeedback : "",
                maintenanceFeedback != null ? maintenanceFeedback : "");

        String result = resultWithScope.result();
        AgenticScope scope = resultWithScope.agenticScope();

        // Update the car's condition with the result from CarConditionFeedbackAgent
        String newCondition = (String) scope.readState("carCondition");
        if (newCondition != null && !newCondition.isEmpty()) {
            carInfo.setCondition(newCondition);
        }

        // Set car status to available if no actions are required
        AgentType selectedAgent = selectAgent(scope);

        if (selectedAgent == AgentType.NONE) {
            carInfo.setStatus(CarStatus.AVAILABLE);
        }

        return result;
    }

    /**
     * Determines which agent should be selected based on the requirements in the AgenticScope
     *
     * @param agenticScope The current AgenticScope containing request states
     * @return The appropriate AgentType to handle the car
     */
    private static AgentType selectAgent(AgenticScope agenticScope) { // (6)
        AgentType result;

        // Check maintenance first (higher priority)
        if (isRequired(agenticScope, "maintenanceRequest")) {
            result = AgentType.MAINTENANCE;
        }
        // Check car wash last (lower priority)
        else if (isRequired(agenticScope, "carWashRequest")) {
            result = AgentType.CAR_WASH;
        }
        // No agent required
        else {
            result = AgentType.NONE;
        }

        System.out.println("selectAgent: " + result);
        return result;
    }

    private static boolean isRequired(AgenticScope agenticScope, String key) {
        String s = (String)agenticScope.readState(key);
        boolean required = s != null && !s.isEmpty() && !s.toUpperCase().contains("NOT_REQUIRED");
        return required;
    }
}

Define the MaintenanceAgent

Notice the definition of the MaintenanceAgent (1), associating it with the injected MaintenanceTool.

Define the MaintenanceFeedbackAgent

Notice the definition of the MaintenanceFeedbackAgent (2).

Define a parallel workflow, FeedbackWorkflow, including the CarWashFeedbackAgent and MaintenanceFeedbackAgent

Notice, in the CarManagementService, that we’ve defined the FeedbackWorkflow (3) using the agent interface we created earlier. This is a parallel workflow that runs both feedback agents simultaneously to analyze the car’s condition from different perspectives.

Define a conditional workflow, ActionWorkflow, including the CarWashAgent and MaintenanceAgent

Also in the CarManagementService, notice the definition of the ActionWorkflow (4), which is a conditional workflow including the maintenance agent and the car wash agent as subagents. Conditional workflows are sequence workflows where each agent in the workflow is paired with a condition that must evaluate to true in order for the agent to be called (otherwise the agent is skipped).

The maintenance agent will only execute if the selectAgent (6) method indicates maintenance is required. The car wash agent will only execute if the selectAgent method indicates a car wash is required. The selectAgent method looks at the values of maintenanceRequest and carWashRequest, in the agentic scope, to make the determination. In this way, we have used conditions to build a kind of router, choosing which of the agents to run for each request.

Modify the sequence workflow, to include the feedback workflow, the action workflow and the car condition feedback agent

Finally, in the code above, we redefined the CarProcessingWorkflow (5) to sequentially run the feedback workflow, the action workflow, and the car condition feedback agent:

Notice that the CarProcessingWorkflow is a nested workflow (workflows within workflows).

Try out the new workflow

If you’re working from the section-2/step-03 directory (or you had stopped the application), start the application with the following command:

./mvnw quarkus:dev

In the Returns section of the UI you should now be able to see a Maintenance Return tab in the Returns section. This is where the Miles of Smiles maintenance team will enter their feedback when they are finished working on the car.

Maintenance Returns Tab

On the Maintenance Return tab, for car 3, enter feedback to indicate the scratch (mentioned in the car condition) has been fixed, but the car needs to be cleaned:

buffed out the scratch. car could use a wash now.

Once the request completes, you should see that the car’s status has been updated in the Fleet Status section.

Updated Fleet Status

Take a look at the logs. You should see that the car wash feedback agent and maintenance feedback agent both ran (in parallel, which may be evident from when the responses from those agents were logged). You should then see the car wash agent and car wash tool responses in the log (since there was no need for maintenance, but a car wash was needed). Finally, you should see the response from the car condition feedback agent.