Sequential tasks: two agents in a row
A sequential crew runs its tasks in list order and hands each task the output of the ones before it.
Last updated: 28 Sep, 2026 · CrewAI 1.15
A support desk has two jobs: a clerk who finds an order, and a writer who answers the customer from what the clerk found. Two agents and two tasks in one crew do exactly that, and the writer reads the clerk's result without you wiring anything.
The sequential process
A crew's default process is sequential: tasks run top to bottom, and every later task receives the earlier outputs as context.
crew = Crew(agents=[clerk, writer], tasks=[look, reply]) # process="sequential" by default
reply.context = [look] # optional: name exactly which task outputs this task seesThe two agents
The clerk carries the lookup tool. The writer has none: its job is words, built from what the clerk hands over.
clerk = Agent(role="Order clerk", goal="Find the status of customers' orders",
backstory="You can look up any order in the shop's system.",
llm=ShopLLM(model="shop"), tools=[lookup_order])
writer = Agent(role="Reply writer", goal="Write replies to customers",
backstory="You write short, friendly emails.",
llm=ShopLLM(model="shop"))
The two tasks and the crew
The reply task never mentions the question. It works from the clerk's answer, because the crew passes it forward.
look = Task(description="Find the order in this message: {question}",
expected_output="The order's status.", agent=clerk)
reply = Task(description="Write the customer a reply.",
expected_output="A short, friendly email.", agent=writer)
crew = Crew(agents=[clerk, writer], tasks=[look, reply], verbose=False)
View the code here
from crewai.tools import tool
ORDERS = {"A17": "shipped on 3 March", "C40": "waiting for stock"}
@tool
def lookup_order(order_id: str) -> str:
"""Look up an order's shipping status by its id, such as A17."""
status = ORDERS.get(order_id)
return f"{order_id} {status}." if status else f"{order_id} is not an order we have."
import json
import os
import re
from crewai import BaseLLM
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
os.environ["CREWAI_TRACING_ENABLED"] = "false"
os.environ["CREWAI_DISABLE_VERSION_CHECK"] = "true"
class ShopLLM(BaseLLM):
script: list = []
def supports_function_calling(self):
return True
def call(self, messages, tools=None, **kwargs):
if isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
if self.script:
return self.script.pop(0)
names = [t["function"]["name"] for t in tools or []]
return self.decide(messages, names)
def decide(self, messages, tools):
last = messages[-1]
if last["role"] == "tool":
return last["content"]
text = last["content"]
orders = re.findall(r"\b[A-Z]\d+\b", text)
wanted = "refund_order" if "refund" in text.lower() else "lookup_order"
matches = [name for name in tools if name.endswith(wanted)]
if orders and matches:
args = json.dumps({"order_id": orders[0]})
return [{"id": f"call_{orders[0]}", "type": "function",
"function": {"name": matches[0], "arguments": args}}]
if "working with:" in text:
context = text.split("working with:")[1].strip().split("\n\n")[0]
return f"Dear customer, {context}"
if orders:
return f"I have no way to look up {orders[0]} yet."
return "Hello. Which order is this about?"
Two tasks, one after the other
The whole program. Printing tasks_output shows both steps in order.
import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
from crewai import Agent, Crew, Task
from shop_llm import ShopLLM
from tools import lookup_order
clerk = Agent(role="Order clerk", goal="Find the status of customers' orders",
backstory="You can look up any order in the shop's system.",
llm=ShopLLM(model="shop"), tools=[lookup_order])
writer = Agent(role="Reply writer", goal="Write replies to customers",
backstory="You write short, friendly emails.",
llm=ShopLLM(model="shop"))
look = Task(description="Find the order in this message: {question}",
expected_output="The order's status.", agent=clerk)
reply = Task(description="Write the customer a reply.",
expected_output="A short, friendly email.", agent=writer)
crew = Crew(agents=[clerk, writer], tasks=[look, reply], verbose=False)
result = crew.kickoff(inputs={"question": "Where is my order A17?"})
for output in result.tasks_output:
print(f"{output.agent}: {output.raw}")Order clerk: A17 shipped on 3 March. Reply writer: Dear customer, A17 shipped on 3 March.
What the two tasks produced
- The clerk ran first and its output is the order's status, looked up with the tool.
- The writer ran second and built its email from the clerk's finding, which arrived as context.
- result.raw is the last task's output, so the crew's result is the writer's email.
Choosing what a task sees
context names the tasks whose outputs a task receives. Setting reply.context = [look] here matches the default, but with three tasks it lets the last one read the first and skip the middle.
import os
os.environ["OTEL_SDK_DISABLED"] = "true"
os.environ["CREWAI_DISABLE_TELEMETRY"] = "true"
from crewai import Agent, Crew, Task
from shop_llm import ShopLLM
from tools import lookup_order
clerk = Agent(role="Order clerk", goal="Find the status of customers' orders",
backstory="You can look up any order in the shop's system.",
llm=ShopLLM(model="shop"), tools=[lookup_order])
writer = Agent(role="Reply writer", goal="Write replies to customers",
backstory="You write short, friendly emails.",
llm=ShopLLM(model="shop"))
look = Task(description="Find the order in this message: {question}",
expected_output="The order's status.", agent=clerk)
reply = Task(description="Write the customer a reply.",
expected_output="A short, friendly email.", agent=writer)
crew = Crew(agents=[clerk, writer], tasks=[look, reply], verbose=False)
reply.context = [look]
result = crew.kickoff(inputs={"question": "Where is my order B22?"})
print(result.raw)Dear customer, B22 is not an order we have.
Sequential vs hierarchical
| Sequential | Hierarchical | |
|---|---|---|
| Order of work | The order tasks are listed | A manager decides at run time |
| Who a task hears from | Every earlier task, or its context | Whatever the manager passes on |
| Model calls | One per task | Extra calls for the manager |
| Best when | The steps are known | The order depends on the answer |
When to run tasks in a row
- A pipeline with fixed steps: find the order, then write the reply.
- Any job where a later step needs an earlier step's result and the order never changes.
description with a {question} placeholder as the clerk's task does. Reordering the tasks changes what each one receives.Related
- Previous: Model hooks around the call
- Next: Structured output with output_pydantic
- Reference: Sequential process
- Swap the order of the tasks in the crew and read what the writer gets.
- Set
reply.context = []and see which branch ofdecideanswers. - Add a third task for the writer that shortens the reply.
This is what real progress feels like.