skip to content

CrewAI

You will learn the role-based multi-agent framework: agents defined by role, goal and backstory, tasks with expected outputs, tools, memory and knowledge, and crews running sequential or hierarchical processes. Interviewers ask about CrewAI because it makes the multi-agent design question concrete — who does what, who delegates, and what a single crew run costs.

part ofAI agent & RAG frameworksoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

How do you run a CrewAI Flow and pass initial values into its state?

level: juniorimportance: must knowfreq 68%

answer

  1. instantiate the class, then one call
  2. an awaitable variant exists too
  3. inputs land on state, not on parameters
  4. start methods read them from self.state
  5. a plot call draws the wiring

basics

~20 s

Instantiate the Flow subclass and call kickoff(), or kickoff_async() to await it. Pass a dict as the inputs argument and those values are applied to the flow state before the start methods run. kickoff returns the last executed step's output.

solid answer

~40 s

Running a flow is two lines: `flow = MyFlow()` then `flow.kickoff(inputs={"topic": "batteries"})`. The inputs dict is applied to `self.state` before any `@start` method fires, so start methods read `self.state.topic` rather than taking parameters — and with structured Pydantic state the values are validated against the model on the way in. `kickoff_async()` is the awaitable form for running inside an event loop or alongside other work. The return value is the output of the **last** method that executed, which in a branching flow is not necessarily the step you consider final, so it is normal to read the authoritative result off `flow.state` afterwards. For debugging the wiring rather than the run, `flow.plot()` writes an interactive HTML graph of the steps and edges.

go deeper

for a junior

Be able to write the three lines: construct the flow, call kickoff with an inputs dict, and read the result. Know that inputs land on self.state rather than arriving as method parameters.

for a middle

Explain that inputs are applied to state before start methods run and are validated when state is a Pydantic model, that the return value is the last executed step's output, and that plot renders the wiring without running anything.

for a senior

Show the operational habits: authoritative result on state rather than the return value, the run id in every log line, one flow instance per concurrent run, and explicit retry or compensation because an exception simply ends the path.

for a principal

Frame kickoff as the system's entry point — how a request maps to a run id, where concurrency across runs is actually managed, and what your service does with a flow that fails halfway with real side effects already committed.

## Instantiate, then kick off A Flow subclass is a normal class. You create an instance and call `kickoff()` on it: ``` flow = ReportFlow() result = flow.kickoff(inputs={"topic": "solid-state batteries"}) ``` That call fires every `@start` method, dispatches listeners as steps complete, and returns when nothing is left pending. There is no separate build or compile step and no runner object to construct. ## What inputs actually do The `inputs` dict is not passed to your methods as arguments. It is applied to the flow's state *before* the start methods run. So a start method reads what it needs from `self.state`: ``` @start() def outline(self): return f"Outline for {self.state['topic']}" ``` This matters for two reasons. First, with structured Pydantic state the incoming values go through the model, so types are validated and coerced rather than stored blindly — a run started with the wrong shape fails at kickoff rather than three steps in. Second, it keeps the flow testable: because everything a run needs is on state, a test can construct the flow, set state directly, and call a single step in isolation without going through kickoff at all. ## Async `kickoff_async()` is the awaitable variant, for running a flow from within an event loop — a web handler, or alongside other concurrent work. Steps themselves may be declared `async def`, which is how a single step performs concurrent I/O internally. Note the distinction interviewers like: awaiting the flow does not make your synchronous steps concurrent with each other. Fan-out across steps is expressed structurally, with multiple entry points or branches, and rejoined with an explicit join condition — not by switching the kickoff call. ## What comes back `kickoff()` returns the output of the last method that executed. In a straight chain that is the obvious terminal step. In a branching flow, "last executed" depends on which branch a router chose and how branches interleave, so treating the return value as *the* result is fragile. The habit that survives contact with production is to write the authoritative result onto state and read `flow.state` after the call — the flow instance is still yours, and its state is fully populated. The same object also carries the auto-generated run `id`, which is the value to log next to everything the run produced so a later investigation can tie output back to one execution. ## Errors An exception raised inside a step propagates out of `kickoff()`. Downstream listeners never fire, there is no automatic retry, and there is no partial-success return value — but the state the flow accumulated up to that point is still readable on the instance, which is often enough to see how far it got and what the last successful step wrote. If you need retries or compensation, they are code you write inside the step or the caller. ## Seeing the graph `flow.plot()` generates an interactive HTML visualization of the flow's steps and the edges between them. It reads the declarative wiring, so it works without executing anything. It is the fastest way to catch the two classic wiring mistakes — a listener attached to the wrong trigger, and a branch label with no subscriber, which leaves a step floating with no inbound edge. On a flow with more than a handful of steps, plotting it is cheaper than reading the decorators. ## Running the same flow many times Each flow instance owns one state, so running two requests concurrently means two instances — do not reuse one instance across concurrent kickoffs and expect its state to stay coherent. Constructing a flow is cheap; the expensive parts are the crews inside it. For a batch, instantiate per item and, if you want them concurrent, drive the async form from your own gather rather than expecting the flow to parallelize across runs.

  • Why is reading the result off flow.state safer than using the kickoff() return value?
    Because kickoff returns whatever the last executed method returned, and in a branching flow which method that is depends on the router's choice and how branches interleave. Writing the authoritative result to state and reading flow.state after the call is deterministic regardless of the path the run took.
  • Does kickoff_async() make the flow's steps run concurrently?
    No. It makes the whole flow awaitable so you can run it inside an event loop or alongside other work. Concurrency between steps comes from the graph — multiple entry points or parallel branches, rejoined with an explicit join condition — plus async step bodies for concurrent I/O inside a single step.
  • What does flow.plot() give you, and when is it worth running?
    It writes an interactive HTML graph of the flow's steps and edges from the declarative wiring, without executing anything. It is the quickest way to catch a listener attached to the wrong trigger or a branch label nobody subscribes to, both of which otherwise show up as a flow that quietly does less than you expected.
  • What is left behind when a step raises during kickoff?
    The exception propagates out of kickoff, downstream listeners never fire, and nothing is retried. The flow instance is still yours though, so the state accumulated up to the failure is readable and usually tells you how far the run got and what the last successful step wrote. Retry and compensation are code you add.

saying these in an interview costs you the question

  • Thinking kickoff inputs are passed as method arguments
  • Expecting kickoff_async to parallelize the flow's steps
  • Treating the kickoff return value as the definitive result
  • Assuming a failed step is retried automatically
  • Reusing one flow instance across concurrent runs

context

open as a page

In CrewAI, what does allow_delegation=True add to an Agent, and what does it cost?

level: middleimportance: must knowfreq 64%

basics

~20 s

It appends coworker tools — delegate work and ask a question — to that agent's toolset, built from the crew's other agents and addressed by role string. The cost is extra LLM turns, longer prompts, and lossy handoffs, since only text crosses between agents.

open as a page

In CrewAI, what do an Agent's role, goal and backstory actually change at runtime?

level: middleimportance: must knowfreq 80%

basics

~20 s

Role, goal and backstory are prompt text: CrewAI interpolates them into the system prompt sent to the model for that agent. Role additionally names the agent for delegation and logs. None of the three enforces anything — tools and task assignment do that.

open as a page

What must you configure on a CrewAI Crew before Process.hierarchical will run?

level: middleimportance: must knowfreq 62%

basics

~20 s

A hierarchical CrewAI crew needs a manager: either manager_llm, from which CrewAI builds a default manager agent, or manager_agent, your own Agent. Supplying neither fails validation, and the manager agent must not also appear in the crew's agents list.

open as a page

In CrewAI, what changes when a Crew runs Process.hierarchical instead of Process.sequential?

level: middleimportance: must knowfreq 85%

basics

~20 s

Process.sequential executes the Crew's tasks in the order you listed them, each task seeing earlier output. Process.hierarchical inserts a manager agent that decides which worker gets which task and reviews the result, adding LLM calls, latency and nondeterminism.

open as a page

In a CrewAI Flow, what do the @start and @listen decorators do?

level: middleimportance: must knowfreq 75%

basics

~20 s

@start marks methods that run when the flow is kicked off. @listen marks a method that runs after the method it names returns, and it can receive that return value as its argument. Together they wire steps into an event-driven chain.

open as a page

In CrewAI Flows, what does typed Pydantic state give you over dict state?

level: middleimportance: must knowfreq 62%

basics

~20 s

Declaring the flow as Flow of a Pydantic model makes self.state a validated model with attribute access, declared defaults and type checking. Unstructured state is a plain dict you index by key, where a misspelled key silently creates a new one.

open as a page

In CrewAI, what does setting memory=True on a Crew actually enable?

level: middleimportance: must knowfreq 75%

basics

~20 s

Three stores switch on at once: short-term memory of recent interactions, entity memory about people and things mentioned, and long-term memory of past task results and their evaluations. CrewAI retrieves from all three and injects the result into each task's prompt.

open as a page

In CrewAI, how does Knowledge differ from crew memory at run time?

level: middleimportance: must knowfreq 62%

basics

~20 s

Knowledge is grounding you attach up front and index at kickoff; it is read-only during the run and unchanged by it. Memory is written by the run itself — recent interactions, entities, past task evaluations — and grows every time the crew executes.

open as a page

In a CrewAI Task, what is expected_output for and why is it required?

level: middleimportance: must knowfreq 72%

basics

~20 s

expected_output is the natural-language contract for a finished task: it is injected into the agent's prompt, tells the agent when to stop iterating and what shape the final answer takes, and becomes the text downstream tasks and reviewers judge the result against. CrewAI requires it on every Task.

open as a page

What does output_pydantic change about a CrewAI Task's TaskOutput?

level: middleimportance: must knowfreq 66%

basics

~20 s

Setting output_pydantic on a Task makes CrewAI convert the agent's finished text into that model after the task ends. The resulting TaskOutput still carries the original text in .raw, and additionally exposes a validated model instance in .pydantic, with .output_format marking it as structured.

open as a page

In CrewAI, when should a tool subclass BaseTool with args_schema instead of using @tool?

level: middleimportance: must knowfreq 68%

basics

~20 s

CrewAI's @tool decorator wraps a plain function and derives the argument schema from its signature and the description from its docstring. Subclass BaseTool when you need an explicit Pydantic args_schema, constructor-held state such as clients, or per-tool fields like cache_function.

open as a page

In CrewAI, how do you give one Agent a different LLM from the rest of the crew?

level: juniorimportance: should knowfreq 58%

basics

~20 s

Pass the llm argument to that Agent's constructor — either a CrewAI LLM object such as LLM(model="openai/gpt-4o") or a plain model string. Agents that omit llm fall back to the environment-configured default model, so model choice is per-agent and opt-in.

open as a page

What does CrewAI's Crew.kickoff() return, and what does that object carry?

level: juniorimportance: should knowfreq 58%

basics

~20 s

Crew.kickoff() returns a CrewOutput, not a string. It carries raw (the final task's text), structured pydantic and json_dict forms when a task requested them, tasks_output with one TaskOutput per task, and token_usage for the whole run.

open as a page

How do CrewAI's kickoff, kickoff_async and kickoff_for_each differ when running a Crew?

level: juniorimportance: should knowfreq 50%

basics

~20 s

kickoff(inputs) runs the crew once and returns a CrewOutput. kickoff_async(inputs) returns an awaitable for the same single run. kickoff_for_each(inputs=[...]) runs the crew once per input dict and returns a list of CrewOutput, one per input.

open as a page

In CrewAI, how do you attach a PDF or text file as crew knowledge?

level: juniorimportance: should knowfreq 55%

basics

~10 s

Create a source object such as PDFKnowledgeSource(file_paths=["report.pdf"]) and pass it as Crew(knowledge_sources=[source]). CrewAI resolves those paths inside a knowledge/ folder at the project root, so the file must sit there.

open as a page

How does a CrewAI task in tasks.yaml get wired to a @task method?

level: juniorimportance: should knowfreq 55%

basics

~10 s

In a @CrewBase project, tasks.yaml stores each task's description and expected_output under a named key. A @task-decorated method returns Task(config=self.tasks_config['that_key']), and the decorator registers the returned Task into self.tasks for the crew.

open as a page

In CrewAI, what happens when a Task defines tools and its Agent already has tools?

level: juniorimportance: should knowfreq 52%

basics

~20 s

Tools listed on a CrewAI Task take precedence for that task: the assigned agent works with the task's tool list rather than its own. Leave Task.tools unset to use the agent's tools; set it to narrow or replace the toolset for one step.

open as a page

In a CrewAI CrewBase project, how do agents.yaml and the @agent decorator define an Agent?

level: middleimportance: should knowfreq 46%

basics

~20 s

agents.yaml holds each agent's role, goal and backstory keyed by name; a @CrewBase class points agents_config at that file, and each @agent method builds Agent(config=self.agents_config["name"]) while adding non-serializable parts — llm, tools, callbacks — in Python.

open as a page

In a CrewAI @CrewBase project, how is the Crew assembled from the decorated methods?

level: middleimportance: should knowfreq 45%

basics

~20 s

@CrewBase turns a class into a crew definition: methods decorated as agents and tasks are collected automatically into self.agents and self.tasks, and the @crew method returns a Crew built from those lists plus the process you choose.

open as a page

In CrewAI Flows, when do listeners using or_() and and_() fire?

level: middleimportance: should knowfreq 42%

basics

~20 s

or_() fires the listener each time any of the named steps finishes, so it can run more than once. and_() holds the listener until every named step has finished and then fires it once. or_() is a race, and_() is a join.

open as a page

In CrewAI Flows, how does @router decide which branch runs next?

level: middleimportance: should knowfreq 58%

basics

~20 s

A @router method is triggered like a listener but returns a string label instead of data. Steps decorated with @listen of that exact label run next; branches whose label was not returned simply never execute.

open as a page

Where does CrewAI write crew memory and knowledge embeddings on disk?

level: middleimportance: should knowfreq 50%

basics

~20 s

Everything lands in a CrewAI storage directory: vector-store folders for short-term, entity and knowledge data plus a SQLite file for long-term memory. The location defaults to a per-user application-data path and is overridden with the CREWAI_STORAGE_DIR environment variable.

open as a page

How does CrewAI cache tool results, and what does cache_function control?

level: middleimportance: should knowfreq 44%

basics

~20 s

CrewAI caches tool results during a crew run, keyed by tool and arguments, so an identical repeat call returns the stored value instead of re-executing. Setting cache_function on a tool lets you decide per result whether it is stored; Crew(cache=False) disables caching entirely.

open as a page

A CrewAI agent keeps calling the same tool and never finishes — which Agent settings bound it?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Three Agent-level caps bound it: max_iter limits reasoning iterations (default 20) and forces a best-effort final answer when hit, max_execution_time caps wall-clock seconds, and max_rpm throttles requests per minute. Tool caching and a step_callback let you see the loop happening.

open as a page

A CrewAI crew's token spend tripled after a refactor — how do you find where it went?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Start from CrewOutput.token_usage to confirm the regression per run, then attribute it: capture a run log with output_log_file and verbose, inspect tasks_output, and check whether process, planning or memory changed. Cap the rate with max_rpm while investigating.

open as a page

What does wrapping each crew in a CrewAI Flow step give you over one big crew?

level: seniorimportance: should knowfreq 55%

basics

~20 s

A flow step is ordinary Python: it calls Crew().kickoff(inputs=...) itself, stores the result on self.state, and a router branches on it. Sequencing, retries and skipping become code you control instead of decisions a manager model makes each run.

open as a page

What breaks when you change a CrewAI crew's embedder after memory exists?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Existing vectors were produced by the old model, so the new one either fails outright on a dimension mismatch or, at equal dimensions, silently returns poor matches. Recovery is to reset the affected stores and re-index, or point the crew at a fresh storage directory.

open as a page

How does a CrewAI Task's context field control what it sees, and what does context=[] mean?

level: seniorimportance: should knowfreq 52%

basics

~20 s

context names the upstream Tasks whose results are injected, as text, into this task's prompt. Left unset, a sequential crew feeds a task what came before it; an explicit list restricts it to exactly those tasks; and an explicit empty list isolates the task so it receives no upstream output at all.

open as a page

How does a guardrail on a CrewAI Task work when validation fails?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A Task guardrail is a callable that receives the finished TaskOutput and returns a two-part result: accepted plus a value, or rejected plus an error message. On rejection CrewAI re-runs the task with that message as feedback, up to the task's retry limit, then raises instead of returning bad output.

open as a page

showing 1–30 of 36