CrewAI Task Design Guide
How to write effective tasks that produce reliable, high-quality output from your agents.
Verified against crewai 1.15.22 and 1.15.23 on 2026-10-01. Live-tested with real LLMs on 2026-10-01. For exact current API forms (imports, parameter names, structured output, guardrail signatures) the check-crewai-api skill is the reference; where this skill and it disagree, follow check-crewai-api.
The 80/20 rule: spend 80% of your effort on task design, 20% on agent design. The task is the most important lever you have. A well-designed task with a mediocre agent will outperform a poorly designed task with an excellent agent.
1. Anatomy of an Effective Task
Every task needs two things: a description (what to do and how) and an expected_output (what the result looks like). Both are required - Task(description=...) alone raises ValidationError ... expected_output Field required.
Description — The Instructions
A good description includes:
- What to do — the core action
- How to do it — specific steps or approach
- Context — why this matters, what it feeds into
- Constraints — scope limits, things to avoid
- Inputs — what data or context is available
Expected Output — The Success Criteria
The expected_output tells the agent what "done" looks like. Be specific about:
- Format — bullet points, paragraphs, JSON, table
- Structure — sections, headings, order
- Length — approximate word count or number of items
- Quality markers — citations required, confidence levels, specific fields
2. The Single Purpose Principle
One task = one objective. Never combine multiple operations into a single task.
Each task has one clear objective. The sequential flow passes context automatically.
3. Task Configuration Reference
Essential Parameters
A sequential crew with an agent-less task fails at Crew(...): Sequential process error: Agent is missing in the task ....
Task Dependencies with context
The rule is the same in sequential and hierarchical crews:
- No
context- the task receives the raw output of every task that ran before it. context=[a, b]- the task receives only those tasks' outputs.context=[]- the task receives no prior output at all.
A context entry must be an earlier task in the crew; pointing at a later task fails at Crew(...) ("context dependency on a future task").
Structured Output
Use output_pydantic or output_json when downstream code needs to parse the result:
Important: expected_output is always a string description - never a class name. The Pydantic model goes in output_pydantic, and the expected_output text tells the agent what fields to include. Set only one of output_pydantic / output_json (both raises Only one output type can be set).
Access structured output:
Set output_pydantic in Python, not in tasks.yaml (section 5). Not Task(response_format=...) (silently ignored) or Task(response_model=...) (.pydantic stays None). More: structured-output.md [blocked].
File Output
Task(..., output_file="output/report.md") saves the output; create_directory (default True) creates missing directories. What gets written depends on the output type: the raw text for a plain task, but the JSON of the model when output_pydantic or output_json is set (even if the file is named .md). If you want a human-readable file from a structured task, add a separate formatting task without a model and give it the output_file.
Async Execution
Async tasks start without waiting; consecutive async tasks run concurrently. The next synchronous task waits for every pending async task before it starts, and (with no context) sees all their outputs; give it context=[...] to choose which.
Rules, all enforced:
- Give each concurrently running async task its own agent. Two async tasks on the same agent fail at kickoff with
RuntimeError: Executor is already running. Cannot invoke the same executor instance concurrently. - A crew may end with at most one async task (
The crew must end with at most one asynchronous task) - finish with a synchronous task that gathers the results. - An async task cannot list an async task from the same concurrent run in its own
context, and aConditionalTaskcannot be async.
Human Review
Task(..., human_input=True) pauses for human review before finalizing. The agent produces its answer, then the terminal shows a "Human Feedback Required" panel and reads a line from stdin. Typing feedback makes the agent revise and ask again; an empty line (Enter) accepts the answer. Use for critical outputs that need human approval.
It needs an interactive stdin. In CI, a server, or any run with stdin closed, input() raises EOFError - after the agent's retries, so you also pay for the extra LLM calls. To drive it from a script, pipe the answers in: printf 'Make it shorter.\n\n' | python run.py (one feedback round, then accept). For approval steps that cannot rely on a terminal, see Flow @human_feedback in the build-flow skill.
Do not use human_input=True or Flow @human_feedback to model normal follow-up chat. In conversational Flows, the next user line should be another flow.handle_turn(message, session_id=...) call. Human review is for approving or correcting a specific task/step output before it moves downstream.
Markdown Formatting
Task(..., markdown=True) appends an instruction to the task prompt that the final answer MUST be Markdown (headers, bold/italic, bullet lists, code spans and fenced code blocks).
Callbacks
4. Task Guardrails — Quality Control
Guardrails validate task output before it passes to the next step. If validation fails, the error is fed back to the agent and the task retries, up to guardrail_max_retries (default 3). After that the task raises Task failed guardrail validation after N retries. Last error: ....
Function-Based Guardrails
Return format: (bool, Any) - first element is pass/fail, second is the result on success (a string that becomes the task's raw output - usually output.raw; never None) or the error message on failure. Return the string, not the TaskOutput itself: when the task has output_pydantic and its agent has tools, output.pydantic is still None inside the guardrail, and (True, output) keeps it that way, so result.pydantic ends up None; (True, output.raw) lets crewai convert it (verified on 1.15.22 and 1.15.23). Annotate the return as tuple[bool, Any] or leave it unannotated: -> bool raises If return type is annotated, it must be Tuple[bool, Any] at Task(...). Use guardrail_max_retries, not the deprecated max_retries.
LLM-Based Guardrails
A string guardrail is checked by an extra LLM call made with the task agent's LLM. Good for subjective quality checks. It needs the task to have an agent, or Task(...) raises Agent is required to use non-programmatic guardrails.
Chaining Multiple Guardrails
Guardrails execute sequentially. Each receives the output of the previous guardrail (a string result replaces output.raw for the next one). Mix function-based (deterministic) and LLM-based (subjective) checks. If you set both guardrails=[...] and guardrail=, only the list is used.
5. YAML Configuration (Recommended)
tasks.yaml
Wiring in crew.py
Keep method names identical to YAML keys. YAML context: entries are resolved by calling the @task method of that name, and agent: by the @agent method of that name, so a mismatch fails when the crew is built. description, expected_output, agent, context, output_file, human_input, async_execution and markdown work in YAML; put output_pydantic, output_json, function guardrails and tools in the Python method.
6. Task Dependencies and Context Flow
Tasks run in list order. A task without context receives the raw output of every earlier task.
You don't need context= for that - it's implicit. Use it to narrow or reshape the dependencies:
Conditional Tasks
The condition receives the TaskOutput of the task immediately before it. A skipped conditional task still appears in result.tasks_output, with raw == "". A ConditionalTask cannot be the first task, cannot be async, and a crew cannot consist only of conditional tasks.
7. Task Tools
Tasks can have their own tools that replace the agent's default tools for that specific task:
If tools is set on the task, the agent gets only those tools for that task - the lists are not merged. Leave it unset to use the agent's tools.
Use task-level tools when the task needs tools the agent does not normally have, to restrict the agent for one task, or when the same agent needs different tool sets for different tasks.
8. Variable Interpolation
Use {variable} placeholders in YAML for reusable tasks:
Variables are replaced at kickoff: crew.kickoff(inputs={"topic": "AI Agents", "current_year": "2026", "target_audience": "developers"}).
Common mistakes:
- Missing variable in
inputs→ kickoff raisesValueError: Missing required template variable ... 'audience' not found in inputs dictionary - Using
{{ }}Jinja2 syntax → crewAI uses single braces{ };{{topic}}renders as{AI Agents} - Unused variables in
inputs→ silently ignored (no error)
9. Common Task Design Mistakes
10. Task Design Checklist
Before running a task, verify:
- Description includes what, how, context, and constraints
- Expected output specifies format, structure, and quality markers
- Single purpose — one clear objective per task
- Agent assigned (required in sequential crews and for string guardrails)
- Dependencies set via
contextwhere the default (all prior outputs) is wrong - Tools provided for any task requiring external data
- Structured output set in Python, and
.pydanticasserted non-None - Guardrails set for critical outputs, returning
(bool, value) - Async tasks each on their own agent, followed by a synchronous task
- Variables in YAML match the
inputsdict keys - Expected output is achievable — test with a simple run before adding complexity
References
For deeper dives into specific topics, see:
- Structured Output [blocked] -
output_pydantic,output_json,response_modelandresponse_formatacross LLM, Agent, Task, and Crew levels
For related skills:
- check-crewai-api - current imports, parameter names, structured output and guardrail forms for crewai 1.15.x
- getting-started — project scaffolding, choosing the right abstraction, Flow architecture
- design-agent — agent Role-Goal-Backstory framework, parameter tuning, tool assignment, memory & knowledge configuration
- build-flow - Flow state, routers and
@human_feedback - test-crewai-project - testing guardrails and task wiring offline with a stub LLM
- connect-tools-and-mcp - the tools you attach to tasks, and which
crewai_toolsnames exist - ask-docs - query the live CrewAI docs for questions not covered by these skills


