CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-dev-langchain4j--langchain4j-agentic

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.

Overview
Eval results
Files

sequential.mddocs/workflows/

Sequential Workflows

Execute agents one after another in a defined sequence, with output from each agent available to subsequent agents via AgenticScope.

Declarative @SequenceAgent Reference

Quick Start

import dev.langchain4j.agentic.AgenticServices;

UntypedAgent pipeline = AgenticServices.sequenceBuilder()
    .subAgents(fetchAgent, processAgent, validateAgent)
    .name("data-pipeline")
    .build();

Object result = pipeline.invoke("Process this data");

When to Use Sequential Workflows

Sequential workflows are ideal for:

  • Data processing pipelines where each step depends on the previous
  • Multi-stage workflows with clear sequential dependencies
  • Ordered execution where the sequence of operations matters
  • Transforming data through multiple processing stages
  • Validation chains where validation occurs at specific points

Creating Sequential Workflows

Factory Methods

/**
 * Create untyped sequential agent builder
 * @return SequentialAgentService for building sequential workflows
 */
static SequentialAgentService<UntypedAgent> sequenceBuilder();

/**
 * Create typed sequential agent builder
 * @param agentServiceClass Agent interface class
 * @return SequentialAgentService for building typed sequential workflows
 */
static <T> SequentialAgentService<T> sequenceBuilder(Class<T> agentServiceClass);

Usage Examples:

import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.UntypedAgent;

// Create untyped sequential workflow
UntypedAgent pipeline = AgenticServices.sequenceBuilder()
    .subAgents(fetchAgent, processAgent, validateAgent)
    .name("data-pipeline")
    .description("Sequential data processing pipeline")
    .build();

Object result = pipeline.invoke("Process this data");

// Create typed sequential workflow
interface DataPipeline {
    String process(String input);
}

DataPipeline typedPipeline = AgenticServices.sequenceBuilder(DataPipeline.class)
    .subAgents(fetchAgent, processAgent, validateAgent)
    .build();

String result = typedPipeline.process("Process this");

SequentialAgentService Interface

Complete configuration interface for sequential workflows.

/**
 * Service for sequential agent workflows
 * Extends AgenticService with all base configuration methods
 */
interface SequentialAgentService<T> extends AgenticService<SequentialAgentService<T>, T> {
    // Inherits methods from AgenticService:
    
    /**
     * Build and return the configured workflow
     */
    T build();
    
    /**
     * Set sub-agents to execute sequentially
     */
    SequentialAgentService<T> subAgents(Object... agents);
    
    /**
     * Set sub-agents as executor list
     */
    SequentialAgentService<T> subAgents(List<AgentExecutor> agentExecutors);
    
    /**
     * Set pre-invocation callback
     */
    SequentialAgentService<T> beforeCall(Consumer<AgenticScope> beforeCall);
    
    /**
     * Set workflow name
     */
    SequentialAgentService<T> name(String name);
    
    /**
     * Set workflow description
     */
    SequentialAgentService<T> description(String description);
    
    /**
     * Set output key for AgenticScope
     */
    SequentialAgentService<T> outputKey(String outputKey);
    
    /**
     * Set custom output function
     */
    SequentialAgentService<T> output(Function<AgenticScope, Object> output);
    
    /**
     * Set error handler
     */
    SequentialAgentService<T> errorHandler(Function<ErrorContext, ErrorRecoveryResult> errorHandler);
    
    /**
     * Add agent listener
     */
    SequentialAgentService<T> listener(AgentListener listener);
}

Usage Examples:

UntypedAgent sequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .name("multi-step-processor")
    .description("Processes data through three sequential steps")
    .outputKey("final_result")
    .beforeCall(scope -> {
        scope.writeState("timestamp", System.currentTimeMillis());
        scope.writeState("workflow_id", UUID.randomUUID().toString());
    })
    .output(scope -> {
        // Aggregate results from all stages
        return Map.of(
            "stage1", scope.readState("agent1_output"),
            "stage2", scope.readState("agent2_output"),
            "stage3", scope.readState("agent3_output"),
            "workflow_id", scope.readState("workflow_id")
        );
    })
    .errorHandler(errorContext -> {
        System.err.println("Error: " + errorContext.error().getMessage());
        return new ErrorRecoveryResult("fallback", false);
    })
    .listener(myListener)
    .build();

Sequential Execution Flow

Understanding the sequential workflow execution model:

Execution Steps

  1. Agent 1 is invoked with the initial input
  2. Agent 1's output is stored in AgenticScope (if outputKey specified)
  3. Agent 2 is invoked, can access Agent 1's output from AgenticScope
  4. Agent 2's output is stored in AgenticScope
  5. Agent 3 is invoked, can access outputs from Agent 1 and Agent 2
  6. Process continues until all agents complete
  7. Final agent's output (or custom output function result) is returned

Data Flow Example

// Agent definitions
UntypedAgent fetchAgent = AgenticServices.agentBuilder()
    .chatModel(chatModel)
    .name("fetcher")
    .description("Fetches raw data from source")
    .outputKey("raw_data")
    .build();

UntypedAgent processAgent = AgenticServices.agentBuilder()
    .chatModel(chatModel)
    .name("processor")
    .description("Processes and transforms data")
    .outputKey("processed_data")
    .context(scope -> {
        String rawData = (String) scope.readState("raw_data");
        return "Process this data: " + rawData;
    })
    .build();

UntypedAgent validateAgent = AgenticServices.agentBuilder()
    .chatModel(chatModel)
    .name("validator")
    .description("Validates processed data")
    .context(scope -> {
        String processedData = (String) scope.readState("processed_data");
        String rawData = (String) scope.readState("raw_data");
        return "Validate this processed data: " + processedData +
               " (original: " + rawData + ")";
    })
    .build();

// Sequential workflow
UntypedAgent pipeline = AgenticServices.sequenceBuilder()
    .subAgents(fetchAgent, processAgent, validateAgent)
    .name("etl-pipeline")
    .build();

// Execute - data flows through each stage
Object result = pipeline.invoke("Fetch data from source X");

Declarative Sequential Agents

Define sequential workflows using annotations.

@SequenceAgent Annotation

Usage Examples:

interface DataProcessingSystem {
    // Sequential workflow declaration
    @SequenceAgent(
        name = "etl-pipeline",
        description = "Extract, transform, and load data pipeline",
        outputKey = "processed_data",
        subAgents = {Extractor.class, Transformer.class, Loader.class}
    )
    String runETL(String source);
}

interface Extractor {
    @Agent(name = "extractor", outputKey = "raw_data")
    String extract(String source);
}

interface Transformer {
    @Agent(name = "transformer", outputKey = "transformed_data")
    String transform(AgenticScope scope) {
        String rawData = (String) scope.readState("raw_data");
        // Transform logic
        return transformData(rawData);
    }
}

interface Loader {
    @Agent(name = "loader", outputKey = "load_status")
    String load(AgenticScope scope) {
        String data = (String) scope.readState("transformed_data");
        // Load logic
        return storeData(data);
    }
}

DataProcessingSystem system = AgenticServices.createAgenticSystem(
    DataProcessingSystem.class,
    chatModel
);

String result = system.runETL("database://source");

Configuration

Pre-Invocation Callback

Execute logic before the sequential workflow starts.

/**
 * Set pre-invocation callback
 * @param beforeCall Consumer receiving AgenticScope
 * @return Builder for chaining
 */
SequentialAgentService<T> beforeCall(Consumer<AgenticScope> beforeCall);

Usage Examples:

UntypedAgent sequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .beforeCall(scope -> {
        // Initialize state
        scope.writeState("start_time", System.currentTimeMillis());
        scope.writeState("workflow_id", UUID.randomUUID().toString());
        scope.writeState("environment", System.getenv("ENV"));

        // Log workflow start
        System.out.println("Starting sequential workflow: " +
                         scope.readState("workflow_id"));

        // Load configuration
        Config config = loadConfiguration();
        scope.writeState("config", config);
    })
    .build();

Custom Output

Customize the final output based on AgenticScope state.

/**
 * Set custom output function
 * @param output Function receiving AgenticScope and returning final output
 * @return Builder for chaining
 */
SequentialAgentService<T> output(Function<AgenticScope, Object> output);

Usage Examples:

UntypedAgent sequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .output(scope -> {
        // Aggregate results from all agents
        Object result1 = scope.readState("agent1_output");
        Object result2 = scope.readState("agent2_output");
        Object result3 = scope.readState("agent3_output");

        long startTime = (Long) scope.readState("start_time");
        long duration = System.currentTimeMillis() - startTime;

        return Map.of(
            "results", List.of(result1, result2, result3),
            "duration_ms", duration,
            "workflow_id", scope.readState("workflow_id"),
            "timestamp", System.currentTimeMillis()
        );
    })
    .build();

// Return detailed report
UntypedAgent reportingSequence = AgenticServices.sequenceBuilder()
    .subAgents(dataAgent, analysisAgent, summaryAgent)
    .output(scope -> {
        return new Report(
            scope.readState("data", String.class),
            scope.readState("analysis", Analysis.class),
            scope.readState("summary", String.class),
            scope.agentInvocations()
        );
    })
    .build();

Error Handling

Handle errors during sequential execution.

/**
 * Set error handler for sequential workflow
 * @param errorHandler Function receiving ErrorContext and returning ErrorRecoveryResult
 * @return Builder for chaining
 */
SequentialAgentService<T> errorHandler(Function<ErrorContext, ErrorRecoveryResult> errorHandler);

Usage Examples:

UntypedAgent sequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .errorHandler(errorContext -> {
        Throwable error = errorContext.error();
        AgenticScope scope = errorContext.agenticScope();

        // Log error with context
        System.err.println("Sequential workflow error: " + error.getMessage());
        System.err.println("Failed at stage: " +
                         scope.agentInvocations().size());

        // Decide whether to continue or stop
        if (error instanceof RecoverableException) {
            // Store error and continue
            scope.writeState("error_occurred", true);
            scope.writeState("error_message", error.getMessage());
            return new ErrorRecoveryResult("recovered", true);
        } else if (error instanceof ValidationException) {
            // Stop execution, return error result
            return new ErrorRecoveryResult(
                Map.of("error", error.getMessage(), "valid", false),
                false
            );
        } else {
            // Fatal error, stop execution
            return new ErrorRecoveryResult(null, false);
        }
    })
    .build();

// Error handling with retryThe error handling with retry
UntypedAgent resilientSequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .beforeCall(scope -> {
        scope.writeState("retry_count", 0);
    })
    .errorHandler(errorContext -> {
        AgenticScope scope = errorContext.agenticScope();
        int retryCount = scope.readState("retry_count", 0);

        if (retryCount < 3) {
            scope.writeState("retry_count", retryCount + 1);
            System.err.println("Retrying (attempt " + (retryCount + 1) + ")");
            return new ErrorRecoveryResult("retry", true);
        } else {
            return new ErrorRecoveryResult("max_retries_exceeded", false);
        }
    })
    .build();

Common Patterns

Pattern 1: ETL Pipeline

Extract, Transform, Load data processing.

UntypedAgent etlPipeline = AgenticServices.sequenceBuilder()
    .subAgents(extractor, transformer, validator, loader)
    .name("etl-pipeline")
    .beforeCall(scope -> {
        scope.writeState("pipeline_id", UUID.randomUUID().toString());
        scope.writeState("start_time", System.currentTimeMillis());
    })
    .output(scope -> {
        long duration = System.currentTimeMillis() -
                       (Long) scope.readState("start_time");

        return Map.of(
            "status", "completed",
            "records_processed", scope.readState("record_count"),
            "duration_ms", duration,
            "pipeline_id", scope.readState("pipeline_id")
        );
    })
    .listener(pipelineMonitor)
    .build();

Pattern 2: Multi-Stage Validation

Validate data through multiple validation stages.

UntypedAgent validationPipeline = AgenticServices.sequenceBuilder()
    .subAgents(
        schemaValidator,
        businessRuleValidator,
        securityValidator,
        complianceValidator
    )
    .name("multi-stage-validator")
    .output(scope -> {
        boolean allValid = true;
        List<String> errors = new ArrayList<>();

        for (String key : List.of("schema", "business", "security", "compliance")) {
            Boolean valid = (Boolean) scope.readState(key + "_valid");
            if (valid == null || !valid) {
                allValid = false;
                String error = (String) scope.readState(key + "_error");
                if (error != null) errors.add(error);
            }
        }

        return new ValidationResult(allValid, errors);
    })
    .build();

Pattern 3: Content Generation Pipeline

Generate, refine, and review content.

UntypedAgent contentPipeline = AgenticServices.sequenceBuilder()
    .subAgents(
        draftGenerator,
        contentRefiner,
        factChecker,
        styleEditor,
        finalReviewer
    )
    .name("content-pipeline")
    .beforeCall(scope -> {
        scope.writeState("content_version", 1);
        scope.writeState("revision_history", new ArrayList<String>());
    })
    .output(scope -> {
        String finalContent = (String) scope.readState("final_content");
        List<String> history = (List<String>) scope.readState("revision_history");

        return new ContentResult(
            finalContent,
            history,
            scope.agentInvocations().size()
        );
    })
    .build();

Pattern 4: Research and Analysis Workflow

Research, analyze, synthesize findings.

UntypedAgent researchWorkflow = AgenticServices.sequenceBuilder()
    .subAgents(
        topicResearcher,
        dataCollector,
        statisticalAnalyzer,
        insightGenerator,
        reportWriter
    )
    .name("research-workflow")
    .output(scope -> {
        return new ResearchReport(
            scope.readState("research_data", List.class),
            scope.readState("analysis", Analysis.class),
            scope.readState("insights", List.class),
            scope.readState("report", String.class)
        );
    })
    .listener(researchTracker)
    .build();

Pattern 5: Approval Chain

Multi-level approval workflow.

UntypedAgent approvalChain = AgenticServices.sequenceBuilder()
    .subAgents(
        managerApproval,
        directorApproval,
        vpApproval,
        ceoApproval
    )
    .name("approval-chain")
    .errorHandler(errorContext -> {
        AgenticScope scope = errorContext.agenticScope();

        // If any approval fails, stop the chain
        if (errorContext.error() instanceof ApprovalDeniedException) {
            scope.writeState("approval_status", "DENIED");
            scope.writeState("denied_by", errorContext.error().getMessage());
            return new ErrorRecoveryResult("denied", false);
        }

        return new ErrorRecoveryResult(null, false);
    })
    .output(scope -> {
        String status = (String) scope.readState("approval_status");
        if (status == null) status = "APPROVED";

        return new ApprovalResult(
            status,
            scope.agentInvocations().stream()
                .map(inv -> inv.agentName() + ": " + inv.output())
                .collect(Collectors.toList())
        );
    })
    .build();

Best Practices

1. Use Output Keys for Data Flow

Always specify outputKey for agents that produce data needed by subsequent agents.

UntypedAgent agent1 = AgenticServices.agentBuilder()
    .chatModel(chatModel)
    .name("fetcher")
    .outputKey("fetched_data")  // Make data available to next agents
    .build();

UntypedAgent agent2 = AgenticServices.agentBuilder()
    .chatModel(chatModel)
    .name("processor")
    .outputKey("processed_data")
    .context(scope -> {
        String data = (String) scope.readState("fetched_data");
        return "Process this: " + data;
    })
    .build();

2. Initialize State Early

Use beforeCall to initialize shared state.

UntypedAgent sequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .beforeCall(scope -> {
        // Initialize configuration
        scope.writeState("config", loadConfig());
        scope.writeState("start_time", System.currentTimeMillis());
        scope.writeState("environment", System.getenv("ENV"));
    })
    .build();

3. Handle Errors Gracefully

Provide error handlers for robust workflows.

UntypedAgent sequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .errorHandler(errorContext -> {
        // Log error
        logError(errorContext.error());

        // Decide recovery strategy
        if (isRecoverable(errorContext.error())) {
            return new ErrorRecoveryResult("fallback_value", true);
        } else {
            return new ErrorRecoveryResult(null, false);
        }
    })
    .build();

4. Use Listeners for Monitoring

Add listeners to track execution.

AgentListener monitor = new AgentListener() {
    @Override
    public void afterAgentInvocation(AgentResponse response) {
        System.out.println(response.agentName() + " completed in " +
                         response.duration().toMillis() + "ms");
    }
};

UntypedAgent sequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .listener(monitor)
    .build();

5. Aggregate Results Meaningfully

Provide custom output functions for clear results.

UntypedAgent sequence = AgenticServices.sequenceBuilder()
    .subAgents(agent1, agent2, agent3)
    .output(scope -> {
        return Map.of(
            "final_result", scope.readState("agent3_output"),
            "intermediate_results", List.of(
                scope.readState("agent1_output"),
                scope.readState("agent2_output")
            ),
            "execution_summary", Map.of(
                "total_agents", 3,
                "invocations", scope.agentInvocations().size()
            )
        );
    })
    .build();

See Also

Install with Tessl CLI

npx tessl i tessl/maven-dev-langchain4j--langchain4j-agentic

docs

index.md

tile.json