Large language models struggle when a single prompt handles a repetitive, multi-step standard operating procedure. Compounding probabilities cause drift and unrecoverable errors. A manager-subagent workflow delegates isolated tasks to specialized workers while deterministic code governs the control flow. The manager plans and verifies, workers execute within narrow contexts, and programmatic routing keeps state changes predictable. This architecture isolates failures to single retries and lowers token costs across high-volume pipelines.
The workflow at a glance
flowchart LR
U[User input] --> M[Manager breaks down request]
M --> D[Deterministic router]
D --> W1[Worker agent A]
D --> W2[Worker agent B]
W1 --> V[Manager validates output]
W2 --> V
V --> C{Requirements met?}
C -- no --> M
C -- yes --> S[Synthesize final response]
Workflows versus agents
A workflow is a deterministic sequence of steps where code explicitly controls routing, branching, and execution order. An agent is an autonomous system where a language model dynamically directs its own reasoning, selects tools, and decides subsequent actions at runtime. Workflows prioritize predictable execution for repetitive tasks, while agents prioritize open-ended autonomy.
When to use it
- Repetitive Standard Operating Procedures (SOPs) with multiple independent subtasks, such as customer support triage, multi-document research, or data migrations.
- Processes where errors compound if handled in a single prompt or monolithic context window.
- Pipelines requiring cheaper, specialized models for execution tasks while reserving capable models for orchestration.
Skip it for linear, single-step tasks or quick classification calls where a standard function or single prompt is sufficient.
Step 1: Define the shared state schema
- Do: Model the state data that passes between steps. Define clear fields for the incoming query, task assignments, worker outputs, and routing status using standard schemas such as Pydantic models.
- Output: A typed state contract that serves as the single source of truth across steps.
Step 2: Configure the manager to decompose tasks
- Do: Point your primary model at the incoming request. Constrain its prompt to output a structured list of subtasks. Assign each subtask a target worker role and the minimum context needed to complete it. For guidance on task breakdown, see our guide on AI Task Decomposition for Developers: A Systems Guide.
- Output: A structured JSON or Pydantic list of discrete work units ready for delegation.
Step 3: Dispatch work to isolated subagents
- Do: Run worker agents concurrently across the planned tasks. Give each subagent a dedicated system prompt, restricted tool access, and an isolated context window. Subagents execute independently without awareness of unrelated tasks.
- Output: Structured task responses returned to the central state store.
Step 4: Validate and synthesize through the manager
- Do: Pass worker outputs back to the manager for evaluation. If any task output fails validation criteria or produces incomplete data, instruct the manager to retry that specific subtask. Once complete, synthesize the outputs into the final response.
- Output: A verified final result delivered to the user or downstream system.
In-house state machines versus orchestrator frameworks
Teams typically implement this pattern in two ways:
- In-house state machines: Write a custom Python class managing an explicit
statedictionary and an event loop. This approach avoids external dependencies and maintains total control over execution logic. - Event-driven frameworks: Packages like LlamaIndex Workflows decouple steps using custom event types and type annotations. Frameworks like LangGraph provide cyclic graphs with built-in checkpointing for persistence.
For architectural patterns on orchestrators and workers, review Anthropic's research on building effective agents.
Review of Python workflow packages
Five frameworks cover orchestrating repetitive LLM tasks:
- LangGraph: Graph-based orchestration modeling steps as nodes and edges. It supports cyclic execution, state persistence, and human checkpoints.
- LlamaIndex Workflows: Event-driven, step-based framework using type annotations to route events asynchronously without graph boilerplate.
- CrewAI: Multi-agent framework organizing collaborative role-playing teams with structured state tracking and hierarchical execution.
- Temporal: General-purpose durable execution platform that runs resilient workflows across outages with automatic retries and state replay.
- Prefect: Workflow orchestrator offering task execution, scheduling, retries, and observability with minimal configuration.
These tools share five core architectural features:
- State management: Tracking and persisting application context across steps.
- Error handling: Retrying transient API failures without restarting the pipeline.
- Control flow: Programmatic rules for branching, parallel execution, and loops.
- Task abstraction: Isolating discrete operations into reusable steps or nodes.
- Observability: Logging and tracing step execution for debugging.
In-house and framework workflow code
Below is the same query triage and resolution workflow implemented first as an in-house Python state machine, then using an event-driven framework.
In-house custom state machine
from typing import Dict, Any
class InHouseSupportWorkflow:
def __init__(self, user_query: str):
self.state: Dict[str, Any] = {
"query": user_query,
"category": None,
"resolution": None,
"step": "triage",
}
def _call_llm(self, prompt: str) -> str:
if prompt.startswith("Categorize"):
return "billing" if "billing" in self.state["query"].lower() else "general"
return "Drafted response to query."
def triage_step(self):
prompt = f"Categorize this query as 'billing' or 'general': {self.state['query']}"
self.state["category"] = self._call_llm(prompt)
self.state["step"] = "resolve"
def resolve_step(self):
if self.state["category"] == "billing":
self.state["resolution"] = "Redirected to billing portal link."
else:
prompt = f"Answer this general query: {self.state['query']}"
self.state["resolution"] = self._call_llm(prompt)
self.state["step"] = "complete"
def run(self) -> str:
while self.state["step"] != "complete":
if self.state["step"] == "triage":
self.triage_step()
elif self.state["step"] == "resolve":
self.resolve_step()
return self.state["resolution"]
Event-driven framework (LlamaIndex Workflows)
import asyncio
from llama_index.core.workflow import Workflow, step, StartEvent, StopEvent, Event
class TriageEvent(Event):
category: str
query: str
class ResolveEvent(Event):
resolution: str
class SupportWorkflow(Workflow):
@step
async def triage(self, ev: StartEvent) -> TriageEvent:
query = ev.get("query")
category = "billing" if "billing" in query.lower() else "general"
return TriageEvent(category=category, query=query)
@step
async def resolve(self, ev: TriageEvent) -> ResolveEvent:
if ev.category == "billing":
resolution = "Redirected to billing portal link."
else:
resolution = f"Drafted response to: {ev.query}"
return ResolveEvent(resolution=resolution)
@step
async def finalize(self, ev: ResolveEvent) -> StopEvent:
return StopEvent(result=ev.resolution)
Variations
- Fixed-route pipeline: Replace dynamic manager planning with hardcoded Python routing if subtasks follow a static sequence every run.
- Human-in-the-loop checkpoint: Pause execution before synthesis to require human sign-off on sensitive actions, as detailed in AI Agent Oversight: Why Passive Approval Fails Systems.
- Tiered models: Assign lighter, faster models to worker tasks and reserve frontier models exclusively for manager decomposition and final verification.