Multi-Agent

July 18, 2026 · View on GitHub

Conductor has one agent primitive — Agent — and multiple strategies for composing agents together. Pick the strategy that matches your workflow's structure.

Strategy overview

StrategyDescriptionUse when
SEQUENTIALAgents run one after another; output of each feeds the nextLinear pipelines (research → write → edit)
PARALLELAll sub-agents run concurrently; results are synthesizedIndependent parallel tasks
HANDOFFSub-agent is called as a tool; parent LLM decides when and whichDynamic routing by LLM
ROUTERA dedicated router agent decides which sub-agent runsRule-based or LLM-based routing
SWARMAny agent can transfer control to another based on triggersOpen-ended conversation routing
ROUND_ROBINSub-agents take turns in a fixed cycleStructured multi-agent discussions
RANDOMA randomly-selected sub-agent runs each turnVaried multi-agent discussions
PLAN_EXECUTEA planner agent produces a structured plan; steps execute against itComplex multi-step tasks with dependencies
MANUALNo automatic orchestration; you drive the loopCustom control flow

Sequential

Agent researcher = Agent.builder()
    .name("researcher").model("anthropic/claude-sonnet-4-6")
    .instructions("Research the topic and return key facts.")
    .build();

Agent writer = Agent.builder()
    .name("writer").model("anthropic/claude-sonnet-4-6")
    .instructions("Write a 200-word article from the research notes.")
    .build();

// Shorthand
Agent pipeline = researcher.then(writer);

// Equivalent explicit form
Agent pipeline = Agent.builder()
    .name("research_pipeline")
    .model("anthropic/claude-sonnet-4-6")
    .agents(researcher, writer)
    .strategy(Strategy.SEQUENTIAL)
    .build();

AgentResult result = runtime.run(pipeline, "Write about the history of jazz");

Parallel

Sub-agents run concurrently. A synthesizer (the parent's LLM) combines their outputs.

Agent french   = Agent.builder().name("french_translator").model("anthropic/claude-sonnet-4-6")
    .instructions("Translate to French.").build();
Agent spanish  = Agent.builder().name("spanish_translator").model("anthropic/claude-sonnet-4-6")
    .instructions("Translate to Spanish.").build();
Agent german   = Agent.builder().name("german_translator").model("anthropic/claude-sonnet-4-6")
    .instructions("Translate to German.").build();

Agent translator = Agent.builder()
    .name("multi_translator")
    .model("anthropic/claude-sonnet-4-6")
    .agents(french, spanish, german)
    .strategy(Strategy.PARALLEL)
    .synthesize(true)                      // merge sub-agent outputs
    .build();

Handoff

Sub-agents are tools the parent's LLM can call. The LLM decides dynamically which agent to invoke and when.

Agent mathAgent = Agent.builder()
    .name("math_agent").model("anthropic/claude-sonnet-4-6")
    .instructions("Solve math problems.").build();

Agent textAgent = Agent.builder()
    .name("text_agent").model("anthropic/claude-sonnet-4-6")
    .instructions("Summarise or rewrite text.").build();

Agent dispatcher = Agent.builder()
    .name("dispatcher")
    .model("anthropic/claude-sonnet-4-6")
    .instructions("Route requests to the appropriate specialist.")
    .agents(mathAgent, textAgent)
    .strategy(Strategy.HANDOFF)            // default strategy
    .build();

Router

A dedicated router agent reads the input and selects which sub-agent runs. The router's output is the name of the sub-agent to invoke.

Agent router = Agent.builder()
    .name("intent_router")
    .model("anthropic/claude-sonnet-4-6")
    .instructions("Reply with exactly one word: 'math', 'code', or 'text'.")
    .build();

Agent parent = Agent.builder()
    .name("smart_dispatcher")
    .model("anthropic/claude-sonnet-4-6")
    .router(router)
    .agents(mathAgent, codeAgent, textAgent)
    .strategy(Strategy.ROUTER)
    .build();

Swarm

Agents transfer control to each other based on text mentions or tool results. Good for conversational routing.

import org.conductoross.conductor.ai.handoff.OnTextMention;
import org.conductoross.conductor.ai.handoff.OnToolResult;

Agent support = Agent.builder()
    .name("support_agent")
    .model("anthropic/claude-sonnet-4-6")
    .instructions("Handle general support. Transfer billing issues to billing_agent.")
    .handoffs(
        OnTextMention.of("billing", "billing_agent"),
        OnTextMention.of("refund",  "billing_agent"),
        OnToolResult.of("escalate_tool", "escalation_agent")
    )
    .build();

Agent billing = Agent.builder()
    .name("billing_agent").model("anthropic/claude-sonnet-4-6")
    .instructions("Handle billing questions.").build();

Agent team = Agent.builder()
    .name("support_team")
    .model("anthropic/claude-sonnet-4-6")
    .agents(support, billing)
    .strategy(Strategy.SWARM)
    .build();

Handoff triggers

ClassFactoryTriggers when
OnTextMentionOnTextMention.of(text, target)Agent output contains text
OnToolResultOnToolResult.of(toolName, target)Tool toolName returns any result
OnToolResultOnToolResult.of(toolName, target, contains)Tool result contains contains
OnConditionnew OnCondition(target, predicate)Custom Java predicate on the message map

Plan-Execute

A planner agent produces a structured Plan; a separate executor runs each step. Steps can have dependencies and run in parallel when safe to do so.

import org.conductoross.conductor.ai.plans.*;

// Each Op names a tool/worker and passes args. Use new Ref("stepId") to wire a
// later step's input to an earlier step's output. For an LLM-generated step,
// pass a Generate spec: .generate(Generate.builder().instructions("...").build()).
Plan plan = Plan.builder()
    .step(Step.builder("fetch_data")
        .operation(Op.builder("get_data").args(Map.of("source", "database")).build())
        .build())
    .step(Step.builder("analyse")
        .dependsOn("fetch_data")
        .operation(Op.builder("analyse_data")
            .args(Map.of("rows", new Ref("fetch_data")))   // consumes fetch_data's output
            .build())
        .build())
    .step(Step.builder("summarise")
        .dependsOn("analyse")
        .operation(Op.builder("summarise")
            .args(Map.of("analysis", new Ref("analyse")))
            .build())
        .build())
    .build();

Agent planExecuteAgent = Agent.builder()
    .name("research_pac")
    .model("anthropic/claude-sonnet-4-6")
    .strategy(Strategy.PLAN_EXECUTE)
    .build();

AgentResult result = runtime.run(planExecuteAgent, "Analyse last month's sales", plan);

Termination conditions

Stop a multi-agent loop early without hitting maxTurns:

import org.conductoross.conductor.ai.termination.*;

// Stop after 5 messages
Agent agent = Agent.builder()
    .termination(MaxMessageTermination.of(5))
    .build();

// Stop when output contains "DONE"
Agent agent = Agent.builder()
    .termination(StopMessageTermination.of("DONE"))
    .build();

// Compose with AND / OR
TerminationCondition cond = MaxMessageTermination.of(10)
    .or(StopMessageTermination.of("FINISHED"));

Agent agent = Agent.builder()
    .termination(cond)
    .build();

See Termination for the full list.