Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.swarms.world/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The PlannerWorkerSwarm implements a planner-worker-judge architecture for parallel multi-agent task execution. Based on Cursor’s “Scaling long-running autonomous coding” research, it separates planning from execution: a planner decomposes goals into prioritized tasks, worker agents claim and execute tasks concurrently from a shared queue, and a judge evaluates the cycle results. The swarm follows a cycle-based workflow:
  1. Planning: A planner agent decomposes the goal into concrete, prioritized tasks with dependencies
  2. Execution: Worker agents independently claim tasks from a shared queue and execute them concurrently via ThreadPoolExecutor — no worker-to-worker coordination
  3. Evaluation: A judge agent evaluates the combined results and decides: complete, fill gaps, or fresh start
  4. Iteration: If not complete, the planner receives judge feedback and produces new tasks for the next cycle

Installation

pip install -U swarms

Attributes

name
str
default:"PlannerWorkerSwarm"
Name identifier for this swarm instance
description
str
default:"A planner-worker execution swarm"
Description of the swarm’s purpose
agents
List[Union[Agent, Callable]]
required
Worker agents that execute tasks. Must not be empty.
max_loops
int
default:"1"
Maximum planner-worker-judge cycles (must be greater than 0)
planner_model_name
str
default:"gpt-5.4"
Model for the planner agent
judge_model_name
str
default:"gpt-5.4"
Model for the judge agent
max_planner_depth
int
default:"1"
Max recursive sub-planner depth. 1 = no sub-planners; 2 = CRITICAL tasks are decomposed once.
worker_timeout
Optional[float]
default:"None"
Max seconds for the entire worker pool per cycle
task_timeout
Optional[float]
default:"None"
Max seconds per individual task execution
max_workers
Optional[int]
default:"None"
Max concurrent worker threads. Defaults to min(len(agents), os.cpu_count()).
output_type
OutputType
default:"dict-all-except-first"
Format for the final result
autosave
bool
default:"False"
Whether to save conversation history
verbose
bool
default:"False"
Enable verbose logging
Raises:
ExceptionCondition
ValueErrorIf no agents are provided or max_loops <= 0

Methods

run()

Executes the planner-worker-judge cycle up to max_loops times or until the judge declares the goal complete.
def run(self, task: Optional[str] = None, img: Optional[str] = None) -> Any
Parameters:
  • task (str): The goal to accomplish
  • img (str, optional): Optional image input
Returns: Formatted conversation history per output_type Raises:
  • ValueError: If task is not provided

get_status()

Returns a structured status report of the swarm and its task queue.
def get_status(self) -> Dict[str, Any]
Returns: Status dict with name, original_task, and queue (containing total, progress, status_counts, and per-task details)

Usage Examples

Quick Start

from swarms import Agent
from swarms.structs.planner_worker_swarm import PlannerWorkerSwarm

swarm = PlannerWorkerSwarm(
    agents=[
        Agent(agent_name="Research", agent_description="Gathers information", model_name="gpt-5.4", max_loops=1),
        Agent(agent_name="Analysis", agent_description="Analyzes data", model_name="gpt-5.4", max_loops=1),
    ],
    max_loops=1,
    max_workers=2,
    worker_timeout=120,
)

result = swarm.run("What are the top 3 benefits of renewable energy?")
print(result)

Multi-Cycle with Judge Feedback

Set max_loops > 1 so the judge can request additional planning cycles when the goal is not yet achieved:
from swarms import Agent
from swarms.structs.planner_worker_swarm import PlannerWorkerSwarm

workers = [
    Agent(
        agent_name="Research-Agent",
        agent_description="Gathers factual information and data",
        model_name="gpt-5.4",
        max_loops=1,
    ),
    Agent(
        agent_name="Analysis-Agent",
        agent_description="Analyzes data and identifies patterns",
        model_name="gpt-5.4",
        max_loops=1,
    ),
]

swarm = PlannerWorkerSwarm(
    name="Research-Swarm",
    agents=workers,
    max_loops=3,           # up to 3 planner-worker-judge cycles
    max_workers=5,
    worker_timeout=120,
)

result = swarm.run(
    task="Produce a comprehensive market report on the EV industry "
    "covering manufacturers, technology trends, adoption challenges, "
    "regional differences, and a 5-year outlook."
)
print(result)
The judge evaluates each cycle:
  • Cycle 1: Judge finds gaps (“missing regional analysis”) — planner creates targeted tasks
  • Cycle 2: Judge finds remaining issues (“outlook section too shallow”) — planner fills gaps
  • Cycle 3: Judge marks complete with quality 9/10

Recursive Sub-Planners

Set max_planner_depth > 1 to automatically decompose CRITICAL-priority tasks via sub-planner agents:
swarm = PlannerWorkerSwarm(
    agents=workers,
    max_planner_depth=2,   # CRITICAL tasks decomposed once
)

result = swarm.run(
    task="Design and implement a complete REST API for a task management system"
)
When the top-level planner produces a CRITICAL task (e.g., “Design the database schema and API endpoints”), it gets cancelled and replaced by the sub-planner’s more granular subtasks.

Timeouts

swarm = PlannerWorkerSwarm(
    agents=workers,
    worker_timeout=120,    # 2 min max for entire worker phase per cycle
    task_timeout=30,       # 30s max per individual task (detects stuck workers)
)
  • worker_timeout: Total wall time for the worker pool per cycle. Workers stop claiming new tasks after this deadline.
  • task_timeout: Per-task execution limit. If exceeded, the task fails with a TimeoutError and may be retried.

Checking Swarm Status

status = swarm.get_status()
print(f"Progress: {status['queue']['progress']}")
for task in status["queue"]["tasks"]:
    print(f"  [{task['status']}] {task['title']} -> {task['assigned_worker']}")

SwarmRouter Integration

PlannerWorkerSwarm is available as a swarm type in SwarmRouter:
from swarms import Agent
from swarms.structs.swarm_router import SwarmRouter

workers = [
    Agent(agent_name="W1", model_name="gpt-5.4", max_loops=1),
    Agent(agent_name="W2", model_name="gpt-5.4", max_loops=1),
]

router = SwarmRouter(agents=workers, swarm_type="PlannerWorkerSwarm")
result = router.run("Analyze the competitive landscape of cloud computing providers")

Architecture

Design Principles (from the Cursor blog)

Cursor PrincipleImplementation
Planners plan, workers execute_run_planner() produces PlannerTaskSpec; workers get WORKER_SYSTEM_PROMPT enforcing “only execute, never plan”
No worker-to-worker coordinationWorkers interact only with TaskQueue.claim() — atomic, no shared state
No locks / optimistic concurrencyTaskQueue uses a version field per task; claim() is atomic under a minimal lock, but workers never block each other
Judge-driven cycles with fresh startCycleVerdict.needs_fresh_start triggers TaskQueue.clear() to combat accumulated drift
Prompts matter more than infrastructureWORKER_SYSTEM_PROMPT, PLANNER_SYSTEM_PROMPT, and JUDGE_SYSTEM_PROMPT enforce strict role boundaries
Horizontal scalingThreadPoolExecutor(max_workers=N) — tested with 200 tasks across 100 threads, zero double-claims

Task State Machine

PENDING --> CLAIMED --> RUNNING --> COMPLETED
                          |
                          v
                        FAILED --> PENDING (retry)
                          |
                          v (retries exhausted)
                        FAILED (permanent)

Any non-terminal --> CANCELLED

Cycle Flow

Cycle 1:
  Planner receives: original task
  Workers execute: tasks from queue
  Judge evaluates: complete? gaps? drift?

Cycle 2+ (if not complete):
  If fresh start: clear ALL tasks, planner starts from scratch with judge feedback
  If gap fill: clear non-terminal tasks, preserve completed results
  Planner receives: original task + judge feedback + identified gaps
  Workers execute: new tasks
  Judge re-evaluates

How It Works

Planner Agent: Created internally each cycle. Uses structured output (PlannerTaskSpec) to produce a plan narrative and a list of concrete tasks with title, description, priority (0-3), and dependency titles. On subsequent cycles, the planner receives the judge’s feedback (gaps + follow-up instructions) appended to the original task. Worker Execution: Each worker runs in a ThreadPoolExecutor thread, independently claiming tasks from a shared TaskQueue. Workers never coordinate with each other. Each worker loop: claims a task, resets agent memory, builds context (WORKER_SYSTEM_PROMPT + task description + dependency results), executes via agent.run(), and marks the task complete or failed. Optimistic concurrency: every task has a version field. State transitions check the expected version — if another worker modified the task, the operation is rejected. This avoids lock-based coordination problems (deadlocks, forgotten releases). Claim priority: highest priority first, then oldest first, with dependency satisfaction required. Judge Agent: Created internally after workers complete. Evaluates the cycle results and produces a CycleVerdict:
FieldTypeDescription
is_completeboolWhether the goal has been fully achieved
overall_qualityint (0-10)Quality score of the combined results
summarystrBrief assessment of what was accomplished
gapsList[str]Specific missing items or issues
follow_up_instructionsOptional[str]Instructions for the planner if another cycle is needed
needs_fresh_startboolWhether accumulated drift requires discarding all prior work

Fresh Start vs Gap Fill

Gap Fill (needs_fresh_start=False)Fresh Start (needs_fresh_start=True)
WhenSome tasks succeeded but gaps remainSystemic drift, contradictory results, fundamentally flawed plan
What happensOnly non-terminal tasks are cleared; completed results are preserved as contextALL tasks are discarded
Planner receivesOriginal task + feedback + gapsOriginal task + feedback (clean slate)

Schemas

PlannerTask

Represents a single task in the shared queue.
FieldTypeDefaultDescription
idstrauto-generatedUnique task identifier (ptask-{uuid})
titlestrrequiredShort, descriptive title
descriptionstrrequiredDetailed description for the worker
priorityTaskPriorityNORMALLOW(0), NORMAL(1), HIGH(2), CRITICAL(3)
depends_onList[str][]Task IDs that must complete first
parent_task_idOptional[str]NoneParent task ID if decomposed by sub-planner
statusPlannerTaskStatusPENDINGCurrent status
assigned_workerOptional[str]NoneName of the worker that claimed this task
resultOptional[str]NoneExecution result
errorOptional[str]NoneError message if failed
retriesint0Retry attempts so far
max_retriesint2Max retries before permanent failure
versionint0Optimistic concurrency counter
created_atfloattime.time()Unix timestamp
completed_atOptional[float]NoneCompletion timestamp
metadataDict{}Arbitrary metadata

TaskPriority

ValueNameDescription
0LOWBackground or optional tasks
1NORMALStandard priority (default)
2HIGHImportant tasks that should be prioritized
3CRITICALMust-do tasks; also triggers sub-planner decomposition when max_planner_depth > 1

Best Practices

Best PracticeDescription
Agent SpecializationGive each worker a specific expertise area (research, analysis, writing) so the planner can match tasks to strengths
Agent DescriptionsProvide clear agent_description fields — the planner uses these to understand what each worker can do
Single-Loop WorkersSet max_loops=1 on worker agents — the swarm’s outer loop handles iteration
Model SelectionUse a capable model for the planner and judge (task decomposition and evaluation are harder than execution)
Reasonable max_loops1-3 cycles is typical; diminishing returns after that
TimeoutsAlways set worker_timeout in production to prevent runaway execution
Worker Countmax_workers should roughly match agent count — more threads than agents won’t help
DependenciesUse task dependencies when output from one task is needed as input to another
Fresh StartTrust the judge’s fresh start mechanism — it’s designed to combat the drift problem identified in Cursor’s research

Error Handling

IssueSolution
No agents providedPass at least one Agent in the agents list
max_loops <= 0Set max_loops to a positive integer
No task providedPass a non-empty task string to run()
Planner output parsing failsCheck that planner_model_name supports structured output (function calling)
Judge output parsing failsDefaults to is_complete=False with quality 0, so the cycle continues safely
Worker stuck on a taskSet task_timeout to detect and fail stuck tasks
All workers idle but tasks remainTasks may be blocked on unmet dependencies — check for circular dependency chains

Performance Considerations

ConsiderationDescription
ParallelismThe primary performance advantage — N workers execute N tasks simultaneously instead of sequentially
Planner/Judge OverheadEach cycle adds 2 LLM calls (planner + judge) on top of the worker calls. Use smaller/faster models for these roles
Sub-Planner Costmax_planner_depth > 1 adds one planner call per CRITICAL task. Only enable when tasks genuinely need decomposition
Memory ResetWorker memory is reset between tasks, which adds a small overhead but prevents context pollution
Task TimeoutPer-task timeout spawns a nested thread — slight overhead, but essential for detecting stuck workers
Queue ContentionThe TaskQueue lock is held only during claim/transition operations (microseconds). Tested with 200 tasks across 100 threads with zero contention issues

Source Code

View the source code on GitHub