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

planner.mddocs/workflows/

Planning-Based Agents

Custom logic-based orchestration where you define the planning algorithm that coordinates agent execution, plus pre-built planning patterns (P2P and GOAP).

Overview

Planning-based agents enable:

  • Custom orchestration logic
  • State-based decision making
  • Complex conditional flows
  • Pre-built planning patterns (P2P, GOAP)
  • Fine-grained control over agent execution

Factory Methods

static PlannerBasedService<UntypedAgent> plannerBuilder();
static <T> PlannerBasedService<T> plannerBuilder(Class<T> agentServiceClass);

Quick Start:

UntypedAgent agent = AgenticServices.plannerBuilder()
    .planner(() -> new MyCustomPlanner())
    .subAgents(agent1, agent2, agent3)
    .build();

Planner Interface

interface Planner {
    default void init(InitPlanningContext context) {
        // Optional initialization
    }

    default Action firstAction(PlanningContext context) {
        return nextAction(context);
    }

    Action nextAction(PlanningContext context);

    default AgenticSystemTopology topology() {
        return AgenticSystemTopology.SEQUENCE;
    }

    default boolean terminated() {
        return false;
    }

    // Helper methods
    default Action noOp() { return new NoOpAction(); }
    default Action call(AgentInstance... agents) { return new AgentCallAction(List.of(agents)); }
    default Action done() { return new DoneAction(); }
    default Action done(Object result) { return new DoneWithResultAction(result); }
}

Action Types

class AgentCallAction extends Action {
    public List<AgentInstance> agents();
}

class DoneAction extends Action {
    // Signals completion
}

class DoneWithResultAction extends Action {
    public Object result();
}

class NoOpAction extends Action {
    // No operation
}

PlanningContext

interface PlanningContext {
    AgenticScope agenticScope();
    List<AgentInstance> agents();
    Action lastAction();
    List<Object> lastActionResults();
}

Custom Planner Example

class CustomPlanner implements Planner {
    private int stepCount = 0;

    @Override
    public Action nextAction(PlanningContext context) {
        stepCount++;
        AgenticScope scope = context.agenticScope();

        if (stepCount == 1) {
            // First step: fetch data
            AgentInstance fetcher = findAgent(context.agents(), "fetcher");
            return call(fetcher);
        } else if (stepCount == 2) {
            // Second step: validate
            Object data = scope.readState("fetched_data");
            if (isValid(data)) {
                AgentInstance processor = findAgent(context.agents(), "processor");
                return call(processor);
            } else {
                return done("Invalid data");
            }
        } else if (stepCount == 3) {
            return done(scope.readState("processed_data"));
        }

        return done();
    }

    @Override
    public boolean terminated() {
        return stepCount >= 3;
    }

    private AgentInstance findAgent(List<AgentInstance> agents, String name) {
        return agents.stream()
            .filter(agent -> agent.name().equals(name))
            .findFirst()
            .orElseThrow();
    }
}

Pre-Built Planning Patterns

P2P Planner (Plan-to-Plan)

Automatic planning that extracts required variables from prompts and activates agents dynamically.

class P2PPlanner implements Planner {
    public P2PPlanner();
    public P2PPlanner(int maxAgentsInvocations);
    public P2PPlanner(Predicate<AgenticScope> exitCondition);
    public P2PPlanner(int maxInvocations, Predicate<AgenticScope> exitCondition);
    public P2PPlanner(int maxInvocations, BiPredicate<AgenticScope, Integer> exitCondition);
    public P2PPlanner(ChatModel chatModel, int maxInvocations, Predicate<AgenticScope> exitCondition);
}

Usage:

import dev.langchain4j.agentic.patterns.p2p.P2PPlanner;

// Simple P2P planner
UntypedAgent agent = AgenticServices.plannerBuilder()
    .planner(P2PPlanner::new)
    .subAgents(dataFetcher, dataProcessor, summaryGenerator)
    .build();

// With max invocations
UntypedAgent limited = AgenticServices.plannerBuilder()
    .planner(() -> new P2PPlanner(15))
    .subAgents(agent1, agent2, agent3)
    .build();

// With exit condition
UntypedAgent conditional = AgenticServices.plannerBuilder()
    .planner(() -> new P2PPlanner(
        20,
        scope -> scope.hasState("completed") || scope.hasState("error")
    ))
    .subAgents(agent1, agent2, agent3)
    .build();

How P2P Works:

  1. Extracts required variables from user prompt
  2. Activates agents dynamically based on available state
  3. Continues until all required variables are available or exit condition met

Best for:

  • Natural language prompts
  • Automatic variable extraction
  • LLM-driven orchestration

GOAP Planner (Goal-Oriented Action Planning)

Dependency graph-based planning that automatically determines optimal execution path.

class GoalOrientedPlanner implements Planner {
    public GoalOrientedPlanner();
}

Usage:

import dev.langchain4j.agentic.patterns.goap.GoalOrientedPlanner;

UntypedAgent agent = AgenticServices.plannerBuilder()
    .planner(GoalOrientedPlanner::new)
    .subAgents(
        dataFetcher,      // Produces: raw_data
        dataValidator,    // Requires: raw_data, Produces: validated_data
        dataTransformer,  // Requires: validated_data, Produces: transformed_data
        reportGenerator   // Requires: transformed_data, Produces: report
    )
    .build();

How GOAP Works:

  1. Analyzes agent input/output requirements
  2. Builds dependency graph
  3. Determines shortest path from initial state to goal
  4. Executes agents in dependency order

Best for:

  • Structured data pipelines
  • Clear dependencies
  • Deterministic execution order

Pattern Comparison

FeatureP2P PlannerGOAP PlannerCustom Planner
Planning ApproachLLM-basedGraph analysisYour logic
ConfigurationChatModel optionalNone neededFull control
Exit ConditionsCustomizableGoal-basedYou define
Best ForNatural languageData pipelinesBusiness logic

Complex Multi-Phase Planner

class MultiPhasePlanner implements Planner {
    private enum Phase { GATHER, ANALYZE, DECIDE, EXECUTE }
    private Phase currentPhase = Phase.GATHER;
    private int retryCount = 0;

    @Override
    public Action nextAction(PlanningContext context) {
        AgenticScope scope = context.agenticScope();
        List<AgentInstance> agents = context.agents();

        switch (currentPhase) {
            case GATHER:
                if (!scope.hasState("data_gathered")) {
                    return call(findAgent(agents, "gatherer"));
                }
                currentPhase = Phase.ANALYZE;
                return noOp();

            case ANALYZE:
                if (!scope.hasState("analysis_complete")) {
                    return call(findAgent(agents, "analyzer"));
                }
                currentPhase = Phase.DECIDE;
                return noOp();

            case DECIDE:
                Boolean shouldProceed = scope.readState("should_proceed", Boolean.class);
                if (shouldProceed == null) {
                    return call(findAgent(agents, "decider"));
                } else if (shouldProceed) {
                    currentPhase = Phase.EXECUTE;
                    return noOp();
                } else {
                    return done("Decision was to not proceed");
                }

            case EXECUTE:
                if (!scope.hasState("execution_result")) {
                    return call(findAgent(agents, "executor"));
                } else if (scope.hasState("execution_error") && retryCount < 3) {
                    retryCount++;
                    scope.writeState("execution_result", null);
                    return call(findAgent(agents, "executor"));
                }
                return done(scope.readState("execution_result"));

            default:
                return done();
        }
    }

    @Override
    public boolean terminated() {
        return currentPhase == Phase.EXECUTE && retryCount >= 3;
    }

    private AgentInstance findAgent(List<AgentInstance> agents, String name) {
        return agents.stream()
            .filter(a -> a.name().equals(name))
            .findFirst()
            .orElseThrow();
    }
}

Declarative API

Example:

interface DataProcessingSystem {
    @PlannerAgent(
        name = "data-orchestrator",
        subAgents = {Fetcher.class, Validator.class, Processor.class, Reporter.class}
    )
    String processData(String source);

    @PlannerSupplier
    default Planner planner() {
        return new CustomDataPlanner();
    }
}

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

String result = system.processData("database://prod");

AgentInstance Interface

Access agent metadata in your planner:

interface AgentInstance {
    Class<?> type();
    String name();
    String agentId();
    String description();
    Class<?> outputType();
    String outputKey();
    boolean async();
    List<AgentArgument> arguments();
    AgentInstance parent();
    List<AgentInstance> subagents();
    boolean leaf();
    AgenticSystemTopology topology();
}

Topology Control

enum AgenticSystemTopology {
    SEQUENCE,          // Sequential execution
    PARALLEL,          // Parallel execution
    LOOP,             // Loop execution
    ROUTER,           // Conditional routing
    STAR,             // Supervisor topology
    AI_AGENT,         // AI-powered agent
    NON_AI_AGENT,     // Non-AI agent
    HUMAN_IN_THE_LOOP // Human interaction
}

Control execution topology in your planner:

class ParallelPlanner implements Planner {
    @Override
    public Action nextAction(PlanningContext context) {
        // Call multiple agents
        return call(agents.get(0), agents.get(1), agents.get(2));
    }

    @Override
    public AgenticSystemTopology topology() {
        return AgenticSystemTopology.PARALLEL;
    }
}

When to Use Each Approach

Use P2P Planner when:

  • Working with natural language prompts
  • Need automatic variable extraction
  • Want LLM-driven orchestration
  • Agents have clear descriptions

Use GOAP Planner when:

  • Have clearly defined dependencies
  • Need deterministic execution order
  • Working with data pipelines
  • Want graph-based optimization

Use Custom Planner when:

  • Need business logic in planning
  • Require complex conditional flows
  • Have specific optimization requirements
  • Need full control over orchestration

See Also

  • Supervisor Agents - LLM-based orchestration
  • Loop Workflows - Iterative execution
  • Conditional Workflows - Conditional routing

Install with Tessl CLI

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

docs

index.md

tile.json