« Phase 19 README · Hitchhiker's · Deep Dive · Principal Deep Dive · Core Contributor · Staff Notes
Phase 19 Warmup — CrewAI From First Principles
Who this is for: someone who can write Python and understands the agent loop (Phase 00/01) but has only called CrewAI through its API, or not at all. By the end you will be able to explain — from the mechanism, not the marketing — what
kickoffdoes, how the sequential and hierarchical processes differ and what each costs, how context flows between tasks, and when a Flow beats a Crew. Nothing here needs a GPU, an API key, or the realcrewaipackage; the labs rebuild the engine in pure stdlib with the LLM injected as a pure function.
Table of Contents
- What CrewAI is: role-playing autonomous agents
- The four primitives: Agent, Task, Crew, Process
- The Agent in depth: role, goal, backstory, tools, llm
- The Task in depth: description, expected_output, agent, context
- The sequential process: the mechanism
- Context passing and task chaining
- Input interpolation: one crew, many runs
- The hierarchical process: a manager that delegates and synthesizes
- Sequential vs hierarchical: when each, and the manager cost
- Tools: how agents act on the world
- Flows: event-driven orchestration, distinct from Crews
- Flow decorators: start, listen, router, or_ and and_
- Flow state, persistence, human-in-the-loop, and Crews inside Flows
- CrewAI vs LangGraph vs AutoGen
- Production concerns: determinism, cost, observability
- Common misconceptions
- Lab walkthrough
- Success criteria
- Interview Q&A
- References
1. What CrewAI is: role-playing autonomous agents
CrewAI is a Python framework for building teams of LLM agents. Its founding metaphor is a
crew of specialists: instead of one prompt trying to do everything, you describe several agents —
each with a role ("Senior Research Analyst"), a goal, and a backstory — hand each one
or more tasks, group them into a crew, pick a process (how the crew runs), and call
kickoff. The framework compiles each agent's persona into a system prompt, runs the tasks
according to the process, threads outputs between them, and hands you a result.
Two design commitments shape everything:
- Role-first, not graph-first. Where LangGraph asks you to draw a state machine (Phase 18), CrewAI asks you to cast a team. The unit of thought is a persona doing a job, and the ergonomics are optimized for standing that up in a few dozen lines. This is why CrewAI is the fastest framework to a working multi-agent demo — and why the hard questions are about what the abstraction hides.
- Two layers: Crews and Flows. A Crew is a self-contained unit of agents executing tasks under a process — think "a pipeline that produces a deliverable." A Flow is a separate, event-driven orchestration layer that wires steps (and whole crews) together with branching, state, and persistence. Beginners think CrewAI is Crews; the senior mental model is "Crews for the sequences, Flows for the orchestration." We treat both, because the distinction is the most common CrewAI interview question.
Importantly, CrewAI does not change the fundamentals from Phase 00. Each agent is still an LLM
in a loop; the trust boundary still holds (the model proposes tool calls, your code executes
them); reliability still compounds (0.95^n); and the manager in a hierarchical crew is another
unreliable LLM whose cost you must budget. CrewAI is an ergonomics and orchestration layer over
the same machine. The labs make that concrete by rebuilding the machine.
2. The four primitives: Agent, Task, Crew, Process
Every CrewAI program is assembled from four objects. Hold these precisely:
| Primitive | What it is | Key fields |
|---|---|---|
Agent | a persona wrapped around an LLM | role, goal, backstory, tools, llm, allow_delegation |
Task | a unit of work for an agent | description, expected_output, agent, context, tools, output_pydantic |
Crew | a team of agents executing tasks | agents, tasks, process, manager_llm |
Process | how the crew runs its tasks | sequential or hierarchical |
The mental model is a pipeline of construction: you build agents, attach tasks to them (or, under
hierarchical, leave assignment to a manager), gather both into a crew, choose a process, and run
crew.kickoff(inputs=...). The return value is a CrewOutput with .raw (the final task's
output) and .tasks_output (every task's result, in order). A minimal shape:
from crewai import Agent, Task, Crew, Process
researcher = Agent(role="Researcher", goal="find facts", backstory="meticulous", llm=model)
writer = Agent(role="Writer", goal="write clearly", backstory="crisp", llm=model)
research = Task(description="Research {topic}", expected_output="3 bullets", agent=researcher)
write = Task(description="Write an article", expected_output="500 words", agent=writer)
crew = Crew(agents=[researcher, writer], tasks=[research, write], process=Process.sequential)
result = crew.kickoff(inputs={"topic": "CrewAI"})
print(result.raw) # the writer's article
print(result.tasks_output[0]) # the researcher's bullets
Lab 01 rebuilds exactly this. The one substitution: the real llm=model becomes an injected
policy(prompt, context) so the crew is deterministic and offline — which, not incidentally, is
how you unit-test a real crew.
3. The Agent in depth: role, goal, backstory, tools, llm
An Agent is a persona compiled into a system prompt plus an execution loop. The three persona
fields are not decoration; they are prompt engineering with named slots:
role— the identity the model adopts ("Senior Financial Analyst"). It frames tone, vocabulary, and what the model treats as in-scope.goal— the objective the agent optimizes for across every task it takes on.backstory— context that further steers behavior ("You have 15 years auditing wholesale banking ledgers and distrust unlabeled numbers.").
CrewAI assembles these into the system message, appends the current task's description and
expected_output, and calls the model. In the lab, Agent.render_prompt does this assembly
explicitly so you can see the persona reach the model, and a test asserts it:
def render_prompt(self, description, expected_output):
header = f"You are {self.role}."
if self.goal: header += f" Your goal: {self.goal}."
if self.backstory: header += f" Backstory: {self.backstory}."
prompt = f"{header}\nTask: {description}"
if expected_output: prompt += f"\nExpected output: {expected_output}"
return prompt
Two more fields matter:
tools— functions the agent may call (see §10). Inside a task, a real agent runs a tool-use loop: propose a tool call, your code executes it, the observation returns, repeat, until the agent produces the task output. That inner loop is the ReAct machine from Phase 01; CrewAI owns it for you.llm— the model behind the persona. This is the single non-deterministic part of a crew, which is why every lab injects it.allow_delegation=Trueadditionally lets an agent hand sub-work to a coworker (the mechanism the hierarchical manager uses).
The lesson: an "agent" in CrewAI is persona + tools + model + loop. The persona is how you steer a general model into a specialist; the loop and tools are how it acts; the model is the part you must treat as untrusted and budget for.
4. The Task in depth: description, expected_output, agent, context
A Task is the unit of work, and its fields are where most real bugs live:
description— the instruction, usually with{placeholders}filled fromkickoffinputs.expected_output— a contract describing what "done" looks like ("A JSON list of 3 objects with keys title and url"). This is not cosmetic: it sharpens the model's target and is the anchor for structured-output validation. Undersell it and the agent free-associates.agent— who performs the task. Undersequentialyou set this explicitly; underhierarchicalyou leave it off and the manager assigns.context— the list of prior tasks whose outputs this task should receive. This one field controls the entire information flow of a sequential crew, and §6 is devoted to it.output_pydantic/output_json— ask CrewAI to parse the raw output into a typed object (validation lives here; Phase 02).
The task's result is a TaskOutput carrying raw (the text), the producing agent, and — if you
asked — the parsed structured form. The crew collects these into CrewOutput.tasks_output. In
the lab, TaskOutput keeps the load-bearing fields (description, expected_output, agent,
raw) so tests can assert provenance.
5. The sequential process: the mechanism
Process.sequential is the default and the one you use most. Its contract is exactly what the
name says, and the precision matters:
- Tasks run in the order listed on the crew. Task N does not start until task N-1 finishes.
- No LLM decides control flow. There is no planner, no manager, no branching. The order is the list you wrote. This determinism is the whole point.
- Each task's output is available to later tasks as context (next section).
The engine is a plain loop — this is the heart of Lab 01:
for task in self.tasks:
description = interpolate(task.description, inputs) # fill {placeholders}
expected = interpolate(task.expected_output, inputs)
context = self._build_context(task, prev, outputs) # feed-forward / explicit / isolated
raw = task.agent.execute(description, expected, context) # call the injected LLM
outputs[task] = TaskOutput(..., raw=raw)
prev = outputs[task]
Because nothing here samples, the sequential process is fully deterministic given the model. That is why, when you unit-test a real crew, you stub the LLM and can assert the exact order, the exact context each task saw, and the exact final output. The framework's control flow is testable independently of the model — the same discipline as Phase 18's LangGraph engine.
6. Context passing and task chaining
Here is the single most important knob in a sequential crew, and a favorite "do you actually know
CrewAI?" probe. A task's context field is a three-way switch:
context value | The task receives | Use it for |
|---|---|---|
None (unset, the default) | the previous task's output | a straight pipeline (research → write → edit) |
[t1, t3] (a list of tasks) | exactly those tasks' outputs, concatenated | a fan-in (combine two specific upstream results) |
[] (empty list) | nothing | an isolated step that must not be biased by prior work |
The lab implements the rule directly, and it is worth reading as pseudocode because the edge — a forward reference — is the bug people hit:
def _build_context(self, task, prev, outputs_by_task):
if task.context is None:
return prev.raw if prev is not None else "" # feed-forward ("" for the first task)
parts = []
for src in task.context:
if src not in outputs_by_task: # src hasn't run yet
raise ValueError("context may only reference EARLIER tasks")
parts.append(outputs_by_task[src].raw)
return "\n".join(parts)
Two things to internalize. First, feed-forward is the default, which is why a two-task crew
"just works" — the writer sees the researcher's output without you wiring anything. Second, the
moment you need a fan-in (a summarizer that reads two specific reports, not "the previous
thing"), you set context=[report_a, report_b] explicitly. Getting this wrong is how the summary
task ends up reading only the last report, or how an "independent second opinion" task is quietly
contaminated by the first opinion because you left context at its feed-forward default.
7. Input interpolation: one crew, many runs
A crew definition is static, but you want to run it over a thousand different topics, tickets,
or customers. CrewAI bridges that with interpolation: {placeholder} tokens in a task's
description and expected_output are string-substituted from the inputs dict passed to
kickoff:
Task(description="Research {topic} for {audience}", ...)
crew.kickoff(inputs={"topic": "CrewAI", "audience": "execs"})
# the agent's prompt now reads: "Research CrewAI for execs ..."
A subtlety the lab makes explicit: interpolation must be safe. Task descriptions routinely
contain literal braces — JSON examples, code snippets, format specs — that are not meant to be
substituted. A naive str.format would raise KeyError on the first stray brace. So the lab (and
real CrewAI) substitutes only known keys and leaves everything else untouched:
def interpolate(template, inputs):
out = template
for key, value in inputs.items():
out = out.replace("{" + str(key) + "}", str(value))
return out
This is a small thing that signals whether you've operated CrewAI at scale: the person who has
been paged by a crashing crew knows the str.format trap.
8. The hierarchical process: a manager that delegates and synthesizes
Process.hierarchical inverts the sequential model. You do not pre-assign tasks to agents.
Instead you give the crew a pool of specialist workers and a manager — in real CrewAI, either
a manager_llm (CrewAI auto-builds a manager agent around it) or an explicit manager_agent.
The manager coordinates:
- Delegation — for each task, the manager decides which worker is best suited and hands it over (using CrewAI's built-in Delegate work to coworker tool).
- Execution — the chosen worker performs the task, seeing the work done so far.
- Synthesis — after the workers finish, the manager reconciles their outputs into one final answer.
Lab 02 models this as a Manager with two injected policies — allocator(task, roles, context)
returning a worker role, and synthesizer(goal, outputs) returning the final text — and a loop:
for task in self.tasks:
role = self.manager.allocate(task.description, worker_roles, context) # a manager call
if role not in workers_by_role:
raise ValueError("manager delegated to an unknown worker") # it can hallucinate
output = workers_by_role[role].execute(task.description, expected, context)
...
final = self.manager.synthesize(goal, worker_outputs) # a manager call
Two mechanisms deserve emphasis. Delegation-by-role: the manager addresses a worker by its
role string, so roles must be unique and the manager can hallucinate a role that does not
exist — which must be a hard error, not a silent no-op. Synthesis: the manager sees all
worker outputs and writes the final answer, so the manager is where the crew's coherence (and
incoherence) comes from.
9. Sequential vs hierarchical: when each, and the manager cost
This is the phase's headline tradeoff. Put the two side by side:
sequential | hierarchical | |
|---|---|---|
| task → agent assignment | you set it (agent=) | the manager decides at runtime |
| who controls order/flow | the listed order (no LLM) | a manager LLM |
| LLM calls | N (one per task) | N worker calls + N+1 manager calls |
| determinism | high (given the model) | lower — flow depends on manager judgment |
| failure surface | each task | each task + the manager (single point) |
| debuggability | trivial (fixed pipeline) | you must read the delegation trail |
| best when | the path is known and fixed | you need a coordinator to route across specialists and reconcile |
The number to carry: a hierarchical crew of N tasks makes N+1 manager LLM calls on top of the N
worker calls. The lab surfaces this as CrewOutput.manager_calls so it is telemetry, not
folklore. Every delegation is a manager reasoning step; the final synthesis is one more. On a
5-task crew that is 6 extra LLM round-trips — real latency and real tokens (Phase 14's cost math).
So the senior default is sequential unless you can name why you need the manager. You need hierarchical when tasks genuinely benefit from being routed to the best-suited specialist at runtime and their outputs must be reconciled by a coordinator — e.g., an open-ended research crew where which specialist should tackle a sub-question depends on what earlier steps found. When the path is known, the manager is pure overhead: cost, latency, and a second unreliable LLM in your control flow. This is Phase 00's "least-agentic that works," applied to process choice.
10. Tools: how agents act on the world
An agent that can only emit text is a chatbot. Tools are what let it act — search the web,
query a database, call an API, run code. In CrewAI you attach tools to an agent (tools=[...]) or
to a task, and inside a task the agent runs a tool-use loop: it emits a structured request to
call a tool, CrewAI parses it, executes the tool (crossing the trust boundary — Phase 00),
feeds the observation back, and lets the agent continue until it produces the task output.
Crucially, the tool loop lives inside a single task — it is orthogonal to the process. Whether the crew is sequential or hierarchical, each task may involve several tool calls before its output exists. That is why a "3-task crew" can make far more than 3 LLM calls: each task is itself a ReAct loop.
Tools are also the place the trust boundary bites in CrewAI specifically: an agent's tool call is untrusted, injection-carrying text (Phase 10), so validation, allow-listing, and sandboxing (Phases 02/09) belong on your side of the boundary, exactly as in a hand-rolled agent. CrewAI gives you the ergonomics of registering tools; it does not give you the security — that is still yours. The labs keep tools out of the core engine (they model the orchestration) and leave the in-task tool loop as an Extension, because the tool machinery is Phase 02/03's subject, faithfully reusable here.
11. Flows: event-driven orchestration, distinct from Crews
Here is the conceptual fork that separates people who have shipped CrewAI from people who have demoed it. A Crew is a self-contained task sequence — great for a fixed pipeline, but it has no first-class notion of branching, waiting on an external event, or wiring several crews together. Flows are CrewAI's answer: an event-driven orchestration layer.
A Flow is a Python class whose methods are wired together not by a call graph you write, but by
decorators that declare an event graph. You mark entry points with @start, chain steps with
@listen, branch with @router, and gate with or_/and_. The engine fires each method when its
trigger fires, threading return values along the edges, until nothing is left to run. The mental
model shift:
- A Crew answers "run these tasks and give me the deliverable."
- A Flow answers "when this finishes, do that; if the result looks like X, branch to Y; wait for both A and B before C; persist state so we can resume; and by the way, step 3 is a whole Crew."
Flows are how you build the realistic system from the phase README: a triage crew, a router that branches on a refund amount, a human-approval gate, and persistence so a ticket survives a restart. Flows orchestrate Crews. The Crew is the section of the orchestra; the Flow is the conductor.
12. Flow decorators: start, listen, router, or_ and and_
The event graph is declared with five constructs. Precisely:
@start()— marks an entry point. Every@startmethod runs when you callkickoff. A flow can have several (parallel entry points).@listen(trigger)— the method runs whentriggercompletes, and receives the trigger's return value (if the method declares a parameter).triggeris usually another method (referenced by identity in the class body) or a router label.@router(trigger)— runs aftertrigger, and its return value is emitted as a label that fires any@listen("that-label")methods. This is how a flow branches: the router inspects state and returns"approve"or"revise", and control flows to whichever branch matches.or_(a, b)— a trigger that fires when any ofa,bcompletes (an "either path" join).and_(a, b)— a trigger that fires only when all ofa,bcomplete (a fan-in gate — wait for both parallel branches before proceeding).
Lab 03 builds the engine underneath these. Two design points are worth stating because they show up as interview questions:
How does the engine terminate? Each method fires at most once. Because there are finitely
many methods and none re-fires, the event loop always terminates — even with diamonds (a →
{b, c} → and_(b, c) → d) or apparent cycles. The lab processes the graph in deterministic
rounds: run everything currently ready, recompute which listeners are now satisfied, repeat.
What does a listener receive? For @listen(m), the return value of m. For or_, the output
of whichever trigger fired it. For and_, the output of one of the completed triggers. A method
that declares no parameter simply ignores the payload. The lab decides by inspecting the method's
arity — the same "pass it if you asked for it" convention CrewAI uses.
13. Flow state, persistence, human-in-the-loop, and Crews inside Flows
State. A Flow carries state across its steps, and it comes in two shapes — a distinction interviewers love:
- Unstructured —
self.stateis a plaindict, which CrewAI auto-stamps with anid. Flexible, schemaless, easy to typo a key and never notice. - Structured —
self.stateis a typed model. In real CrewAI that is a PydanticBaseModel(declaredclass MyFlow(Flow[MyState])); it too gets anid. You get validation, autocomplete, and a schema every step agrees on. The lab uses a stdlib@dataclassin its place (we deliberately do not import Pydantic), which preserves the semantics — typed fields that mutate and persist across steps — without the dependency.
Steps read and mutate self.state, and the mutations persist forward: a @start that sets
state.n = 1 and a @listen that does state.n += 1 leaves state.n == 2. That shared, evolving
state is the flow's memory.
Persistence. The @persist decorator saves state after each step so the flow can resume
after a crash or a pause — durable execution (Phase 08) at the flow level. Real CrewAI persists to
SQLite (SQLiteFlowPersistence), keyed by the state id. The lab uses an in-memory store with
the identical save/load-by-id interface; note in the docs which is which. Persistence is also what
makes human-in-the-loop practical: a @router can pause the flow pending an approval, the
state is persisted, and when the human answers hours later the flow resumes from exactly where it
stopped.
Crews inside Flows. The payoff pattern: a @listen method body can run a whole
SomeCrew().kickoff(inputs) and pass its CrewOutput downstream, and a @router can branch on
that output. This is the intended architecture for anything non-trivial — sequential/hierarchical
Crews as the workers, a Flow as the orchestrator that routes between them, persists, and gates
on humans. If you remember one sentence from this warmup: Crews are the sequences; Flows are the
event graph that composes them.
14. CrewAI vs LangGraph vs AutoGen
You will be asked to place CrewAI among its peers. The honest, senior framing:
| Framework | Core abstraction | Reach for it when | The cost |
|---|---|---|---|
| CrewAI | roles + crews + process (and Flows for orchestration) | you want a role-based team stood up fast; role-playing collaboration reads naturally | the process hides control flow; hierarchical adds a manager LLM; less explicit state control than a graph |
| LangGraph | an explicit graph of nodes over shared state (Pregel/BSP super-steps, reducers, checkpointers) | you need precise control over state, branching, cycles, and durable checkpoints — production control | more upfront wiring; you draw the state machine yourself (Phase 18) |
| AutoGen | conversation between agents (a chat among agents, e.g. GroupChat) | the task is naturally a dialogue/negotiation among agents, or human-in-the-loop chat | conversation can meander; harder to make deterministic and cheap |
The one-liners to have ready: CrewAI = fastest to a role-based crew; the abstraction is a team. LangGraph = a graph/state machine; reach for it when you need explicit state and control (durable checkpoints, precise branching, cycles). AutoGen = agents conversing; reach for it when the paradigm really is a conversation. They are not mutually exclusive — a mature stack might use CrewAI for a crew, wrapped in a LangGraph or CrewAI Flow for durable orchestration — and the ability to say why each fits a given task is the actual signal. Naming a framework is commodity; matching it to the task's control-flow needs is seniority.
15. Production concerns: determinism, cost, observability
Framework knowledge is only useful if you can run it in production. Four concerns:
- Determinism. The only non-deterministic part of a crew is the LLM. So you make crews testable by injecting/stubbing the model (record-replay, VCR-style fixtures) exactly as the labs do, and you assert control flow — order, delegation, branch taken, context seen — without paying for tokens. A crew whose control flow correctness depends on the model being right is a crew you cannot test; move that logic into deterministic code (the process, the Flow router) where you can.
- Cost of manager delegation. The hierarchical process's N+1 manager calls are the first
place a CrewAI bill surprises you, right after the per-task tool loops. Instrument
token_usagefrom day one (Phase 14). "We used hierarchical because we needed dynamic routing, and here is the manager-call overhead we accepted" is a defensible sentence; "we used hierarchical because it sounded better" is not. - Observability. A hierarchical crew or a branching Flow is opaque until you can see the
delegation trail and the event graph path. CrewAI integrates with tracing tools
(AgentOps, Langtrace, OpenTelemetry-based backends); the lab's
Delegationrecords andFlow.completedlist are the miniature of what those traces show — who did what, in what order, which branch fired. - When to choose CrewAI at all. Choose it when the task decomposes naturally into roles and a fixed-ish process, when time-to-prototype matters, and when you can keep the risky autonomy (hierarchical, long tool loops) contained. Prefer a plain workflow / LangGraph when you need explicit state and durable, precisely-controlled execution. The framework is a means; the process and Flow choices are where the engineering judgment lives.
16. Common misconceptions
- "CrewAI is Crews." No — CrewAI is Crews and Flows. Crews are self-contained task sequences; Flows are the event-driven orchestration (branching, state, persistence, HITL) that composes crews. Missing Flows is the tell of someone who only did the quickstart.
- "Hierarchical is the 'smart' upgrade over sequential." Hierarchical is more autonomous, which means more cost and more risk: N+1 manager LLM calls and a coordinator you must trust. Sequential is the right default; hierarchical is a deliberate choice you can cost out.
- "The process is just a setting." The process is the execution model — it decides order, who assigns tasks, how many LLM calls happen, and whether the crew is deterministic. It is the first thing you defend in a design review, not a footnote.
- "
contextis automatic, don't worry about it." The default is feed-forward, but the moment you need a fan-in or an isolated step you must setcontextexplicitly — and a forward reference (depending on a task that runs later) is an error. Silent context bugs are the top source of "the crew's output is subtly wrong." - "A
@routerpasses data to the branch." A router routes — it emits a label that selects which@listenfires. Data flows throughself.stateand through@listen(method)payloads, not through the label. - "CrewAI removes the trust boundary / the reliability math." It removes neither. Every agent is still an untrusted LLM in a loop; tool calls still cross the trust boundary; reliability still compounds. CrewAI is ergonomics over the same machine — which is exactly why building the machine (these labs) is what makes your CrewAI knowledge defensible.
17. Lab walkthrough
Do the labs in order; each is a faithful miniature with the LLM injected as a pure policy.
- Lab 01 — Sequential Crew. Build
interpolate, thenAgent(render_prompt+execute),Task, andCrew.kickofffor the sequential process. The crux is_build_context— the three-wayNone/[...]/[]switch of §6. Prove order, context feed-forward vs explicit fan-in vs isolation, interpolation reaching the prompt, and determinism. - Lab 02 — Hierarchical Process. Build
Manager(allocate,synthesize) andCrew.kickofffor the hierarchical process. Watch the two invariants: an unknown-worker delegation is a hard error, andmanager_calls == len(tasks) + 1. Contrast it with lab 01's sequential engine in your head as you go. - Lab 03 — Flows. Build the trigger
Condition(or_/and_), the@start/@listen/@routerdecorators, and the round-basedFlow.kickoffevent loop, plus structured (dataclass) vs unstructured (dict) state and@persist. Prove a router branch, anand_fan-in, that mutation persists across steps, and that the graph terminates.
Run each with LAB_MODULE=solution pytest test_lab.py -v first (green), then fill your lab.py
to match, then read the solution.py main() output.
18. Success criteria
-
You can list the four primitives and say what
Processis (the execution model). -
You can state the three
contextmodes and what each produces, and name the forward- reference error. -
You can explain, from the loop, why
sequentialis deterministic given the model. -
You can compute the hierarchical process's LLM-call overhead (
N+1manager calls) and say when it is worth paying. -
You can wire a
@routerbranch and anand_fan-in by hand, and explain what a@listenmethod receives. -
You can contrast structured vs unstructured Flow state and say what
@persiststores and where (SQLite, keyed byid). - You can give the one-line "reach for it when…" for CrewAI vs LangGraph vs AutoGen.
-
All three labs pass under both
labandsolution(22 + 21 + 21 = 64 tests).
19. Interview Q&A
Q: Walk me through what crew.kickoff() does under the sequential process. A: It runs the
tasks in listed order. For each task it interpolates the inputs dict into the description and
expected output, resolves the task's context (by default the previous task's output; or specific
tasks if context=[...]; or nothing if context=[]), calls the assigned agent's LLM with the
rendered persona+task prompt and that context, and stores the result. It returns a CrewOutput
whose .raw is the last task's output and whose .tasks_output holds every task's result. No LLM
decides control flow — the order is the list — which is why a stubbed model makes the whole thing
deterministic and unit-testable.
Q: Sequential vs hierarchical — when and what does it cost? A: Sequential runs pre-assigned tasks in a fixed order with no coordinator: N LLM calls, deterministic, cheap, easy to debug. Use it whenever the path is known. Hierarchical drops task assignment and adds a manager LLM that delegates each task to a worker and synthesizes the results — so it makes N+1 manager calls on top of the N worker calls, its flow depends on the manager's judgment, and the manager is a single point of failure. Reach for it only when you genuinely need runtime routing across specialists and a coordinator to reconcile them; otherwise it is pure cost and risk.
Q: How does context passing work, and what's the classic bug? A: Each task's context field
controls what upstream output it sees: unset means feed-forward (the previous task), a list means
exactly those tasks (a fan-in), an empty list means isolated. The classic bug is leaving context
at its feed-forward default when you actually needed a fan-in — your summarizer then reads only the
last task instead of the two reports you meant — or the reverse, contaminating a "fresh second
opinion" task with prior output. A task can only reference earlier tasks; a forward reference is
an error.
Q: What is a Flow, and when do you use one instead of a Crew? A: A Crew is a self-contained
task sequence — a pipeline that produces a deliverable. A Flow is event-driven orchestration:
@start/@listen/@router decorators declare an event graph, or_/and_ gate on any/all,
state persists across steps, and @persist (SQLite) makes it resumable. You use a Flow when you
need branching, to wait on external events or a human, to compose multiple crews, or durability.
Rule of thumb: Crews are the sequences; a Flow is the conductor that routes between them, branches,
persists, and gates on humans.
Q: A @listen method — what triggers it and what does it receive? A: It runs when its
trigger completes, where the trigger is another method (referenced by identity) or a router label.
If it declares a parameter, it receives the trigger's return value; a router instead emits a label
string that selects which @listen("label") fires. or_(a,b) fires it when either completes;
and_(a,b) only when both do. Each listener fires at most once, which is why the event graph
always terminates.
Q: Structured vs unstructured Flow state? A: Unstructured state is a plain dict auto-stamped
with an id — flexible but schemaless and typo-prone. Structured state is a typed model (a
Pydantic BaseModel in real CrewAI, declared Flow[MyState]), also id-stamped, giving
validation, autocomplete, and a schema every step shares. Both mutate in place and persist across
steps; @persist snapshots them (to SQLite, keyed by id) so the flow can resume after a crash
or a human pause.
Q: Place CrewAI against LangGraph and AutoGen. A: CrewAI's abstraction is roles and crews — fastest to a role-based team, with Flows for orchestration. LangGraph's abstraction is an explicit graph over shared state with reducers and durable checkpoints — reach for it when you need precise state and control. AutoGen's abstraction is agents conversing — reach for it when the task really is a dialogue. They compose; the signal is matching the abstraction to the task's control-flow needs, not naming a favorite.
Q: You're told to build "an autonomous refund agent." How do you architect it in CrewAI? A:
I'd resist one big autonomous agent. Most of it is a fixed pipeline — triage the ticket, draft a
reply, review it — which is a sequential Crew (deterministic, cheap). I'd wrap that in a Flow
whose @router branches on the refund amount: under the threshold, send; over it,
@listen("needs_human") for approval, with @persist so the ticket survives while it waits. I'd
reserve the hierarchical process for the one sub-task that truly needs a manager to route across
specialists, and I'd instrument token usage because that manager and the tool loops are where the
cost hides. That is least-agentic-that-works applied to CrewAI.
20. References
- CrewAI documentation — concepts (Agents, Tasks, Crews, Processes), Flows, and CLI. https://docs.crewai.com
- CrewAI — Crews vs Flows (when to use each). https://docs.crewai.com/en/guides/concepts/evaluating-use-cases
- CrewAI — Processes (sequential vs hierarchical, manager LLM). https://docs.crewai.com/en/concepts/processes
- CrewAI — Flows (
@start,@listen,@router,or_/and_, state,@persist). https://docs.crewai.com/en/concepts/flows - CrewAI GitHub (source of truth for the primitives and the flow engine). https://github.com/crewAIInc/crewAI
- Anthropic, Building Effective Agents (2024) — workflows vs agents; "least-agentic that works." https://www.anthropic.com/research/building-effective-agents
- LangGraph documentation (the graph/state-machine contrast). https://langchain-ai.github.io/langgraph/
- Microsoft AutoGen documentation (the conversation-between-agents contrast). https://microsoft.github.io/autogen/
- Phase 07 (this track) — multi-agent orchestration from first principles (supervisor/worker/ critic, message bus, typed handoffs) — the primitives CrewAI packages.
- Phase 18 (this track) — LangGraph's StateGraph/Pregel execution model, for the direct contrast.