Battalion Orchestration Patterns

The Battalion system in crates/paladin-battalion/ coordinates multiple Paladin agents through eight distinct execution patterns, plus the Commander strategy router that can select a pattern automatically.


Table of Contents

  1. Overview
  2. Quick Start
  3. The Eight Patterns
  4. Commander — Strategy Router
  5. Error Handling
  6. Performance Notes
  7. Best Practices

Overview

PatternModuleExecutionBest For
Formationformation_serviceSequential (N→N+1)Multi-step pipelines
Phalanxphalanx_serviceConcurrentParallel analysis
Campaigncampaign_serviceDAG / topologicalBranching workflows
Chain of Commandchain_of_command_serviceHierarchical delegationTask routing
Conclaveconclave_execution_serviceParallel experts + aggregatorExpert synthesis
Councilcouncil_serviceTurn-taking dialogueCollaborative consensus
Grovegrove_serviceSemantic routingSpecialist selection
ManeuvermaneuverFlow DSL declarativeDynamic mixed patterns

All services require only Arc<dyn PaladinPort> (from paladin-ports) — they never import LLM provider libraries directly.


Quick Start

[dependencies]
paladin-ai = { version = "0.5.0", features = ["llm-openai"] }
tokio = { version = "1", features = ["full"] }
use paladin_battalion::formation_service::FormationExecutionService;
use paladin_core::platform::container::battalion::formation::Formation;
use paladin_core::platform::container::battalion::BattalionConfig;
use std::sync::Arc;

// Each Paladin is built with PaladinBuilder (see paladin-agents.md)
let paladins = vec![analyzer, processor, summarizer];
let config = BattalionConfig::default();
let formation = Formation::new(paladins, config)?;

let service = FormationExecutionService::new(paladin_port);
let result = service.execute(&formation, "Analyze the Q3 earnings report").await?;
println!("{}", result.output);

The Eight Patterns

Formation — Sequential

Source: crates/paladin-battalion/src/formation_service.rs

Output from each Paladin feeds the input of the next. Ideal for multi-step data transformation pipelines.

use paladin_battalion::formation_service::FormationExecutionService;
use paladin_core::platform::container::battalion::formation::Formation;

let formation = Formation::new(vec![extractor, analyzer, writer], config)?;
let service = FormationExecutionService::new(paladin_port);
let result = service.execute(&formation, "Raw data...").await?;

Configuration keys: sequential timeout, error strategy.


Phalanx — Concurrent

Source: crates/paladin-battalion/src/phalanx_service.rs

All Paladins receive the same input and execute concurrently via tokio tasks. Results are aggregated according to the AggregationStrategy.

use paladin_battalion::phalanx_service::PhalanxExecutionService;
use paladin_core::platform::container::battalion::phalanx::{AggregationStrategy, Phalanx};

let phalanx = Phalanx::new(
    vec![security_auditor, performance_analyst, style_checker],
    AggregationStrategy::Concatenate,
    config,
)?;

let service = PhalanxExecutionService::new(paladin_port);
let result = service.execute(&phalanx, "Review this Rust code...").await?;

AggregationStrategy variants: Concatenate, FirstSuccess, Majority, Custom.

Concurrency is bounded by a tokio::sync::Semaphore (configurable via max_concurrency in BattalionConfig).


Campaign — Graph/DAG

Source: crates/paladin-battalion/src/campaign_service.rs

Paladins are arranged in a directed acyclic graph. Execution is topologically sorted so upstream agents complete before downstream agents begin.

use paladin_battalion::campaign_service::CampaignExecutionService;
use paladin_core::platform::container::battalion::campaign::Campaign;

let campaign = Campaign::builder()
    .add_node("ingest", ingest_paladin)
    .add_node("analyze", analyze_paladin)
    .add_node("report", report_paladin)
    .add_edge("ingest", "analyze")
    .add_edge("analyze", "report")
    .config(config)
    .build()?;

let service = CampaignExecutionService::new(paladin_port);
let result = service.execute(&campaign, "Start").await?;

Independent branches execute concurrently; the service enforces dependency order.


Chain of Command — Hierarchical

Source: crates/paladin-battalion/src/chain_of_command_service.rs

A commander Paladin decomposes the task and routes sub-tasks to specialist Paladins, then synthesizes their outputs.

use paladin_battalion::chain_of_command_service::ChainOfCommandExecutionService;
use paladin_core::platform::container::battalion::chain_of_command::ChainOfCommand;

let chain = ChainOfCommand::new(
    commander_paladin,
    vec![backend_dev, frontend_dev, qa_engineer],
    config,
)?;

let service = ChainOfCommandExecutionService::new(paladin_port);
let result = service.execute(&chain, "Build a login feature").await?;

Conclave — Mixture of Experts

Source: crates/paladin-battalion/src/conclave_execution_service.rs

Multiple expert Paladins process the same task in parallel; an aggregator Paladin synthesizes their outputs into a final response.

use paladin_battalion::conclave_execution_service::ConclaveExecutionService;
use paladin_core::platform::container::battalion::conclave::Conclave;

let conclave = Conclave::new(
    vec![legal_expert, technical_expert, business_expert],
    synthesis_paladin,
    config,
)?;

let service = ConclaveExecutionService::new(paladin_port);
let result = service.execute(&conclave, "Should we adopt microservices?").await?;
// result.aggregated_output contains the synthesized response
// result.successful_expert_count() shows how many experts contributed

Council — Collaborative Discussion

Source: crates/paladin-battalion/src/council_service.rs

Paladins take turns responding to each other in a structured discussion, building toward a shared conclusion or consensus.

use paladin_battalion::council_service::CouncilService;
use paladin_core::platform::container::battalion::council::Council;

let council = Council::new(
    vec![optimist_paladin, skeptic_paladin, moderator_paladin],
    config,  // includes discussion_rounds
)?;

let service = CouncilService::new(paladin_port);
let result = service.execute(&council, "Evaluate adopting async Rust").await?;

Grove — Semantic Routing

Source: crates/paladin-battalion/src/grove_service.rs

The Grove routes the input to the most semantically appropriate Paladin from the registered specialists, using LLM-based capability matching.

use paladin_battalion::grove_service::GroveExecutionService;
use paladin_core::platform::container::battalion::grove::Grove;

let grove = Grove::new(
    vec![python_expert, rust_expert, go_expert],
    config,
)?;

let service = GroveExecutionService::new(paladin_port);
let result = service.execute(&grove, "Help me with Rust lifetimes").await?;
// Routes to rust_expert automatically

Maneuver — Flow DSL

Source: crates/paladin-battalion/src/maneuver/

Maneuver is a declarative flow DSL that lets you compose multiple Battalion patterns in a single workflow definition. See Maneuver Flow DSL for full syntax and examples.


Commander — Strategy Router

Source: crates/paladin-battalion/src/commander.rs

The Commander provides a single entry-point that automatically selects the optimal pattern based on input analysis and the number/capabilities of Paladins provided.

use paladin_battalion::commander::Commander;
use paladin_core::platform::container::battalion::{BattalionConfig, BattalionStrategy};

let commander = Commander::new(paladin_port, paladin_registry);

// Auto-select strategy
let result = commander
    .execute(paladins, "Analyze and summarize this report", BattalionStrategy::Auto, config)
    .await?;

// Or force a specific strategy
let result = commander
    .execute(paladins, "Run in parallel", BattalionStrategy::Phalanx, config)
    .await?;

Auto Mode Heuristics

PriorityPatternTriggers
1Conclave≥3 paladins + keywords: synthesize, compare, perspectives
2Council≥2 paladins + keywords: discuss, debate, consensus, brainstorm
3Grove≥2 paladins + keywords: route, expertise, most qualified
4Formation1–3 paladins by default / keywords: sequential, pipeline
5PhalanxMultiple paladins for parallel analysis
6CampaignComplex multi-step with branching

Error Handling

All services use ErrorStrategy from paladin_core::platform::container::battalion:

StrategyBehaviour
FailFastFirst failure aborts the entire Battalion (default)
ContinueOnErrorFailed agents are skipped; others continue
RetryThenContinueRetry failed agents up to N times, then continue
use paladin_core::platform::container::battalion::{BattalionConfig, ErrorStrategy};

let config = BattalionConfig {
    error_strategy: ErrorStrategy::ContinueOnError,
    max_concurrency: Some(4),
    timeout_seconds: 120,
    ..Default::default()
};

BattalionResult fields: output: String, paladin_results: Vec<PaladinResult>, status: BattalionStatus, execution_time_ms: u64, token_usage: TokenUsage.


Performance Notes

  • Phalanx concurrency is capped by BattalionConfig::max_concurrency (default: unbounded). Set this to avoid overloading upstream LLM rate limits.
  • Formation adds one LLM call per Paladin sequentially — keep chains short (<6) for latency-sensitive workloads.
  • Campaign parallelises independent branches automatically; no manual coordination needed.
  • The Commander auto-router adds a small analysis overhead (~50ms); negligible for most tasks.

Best Practices

  • Formation: Keep the chain ≤5 agents and design each stage to produce clean hand-off text.
  • Phalanx: Set max_concurrency to stay within LLM provider rate limits.
  • Campaign: Validate your DAG has no cycles before deployment (Campaign::build() checks this).
  • Conclave: Ensure expert agents have distinct, non-overlapping system prompts for better synthesis.
  • Council: Include a moderator Paladin to keep discussions on track.
  • Grove: Write precise capability descriptions in each Paladin's agent_description field.
  • Commander Auto: Test your routing decisions with representative inputs before production.