« 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 kickoff does, 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 real crewai package; the labs rebuild the engine in pure stdlib with the LLM injected as a pure function.

Table of Contents

  1. What CrewAI is: role-playing autonomous agents
  2. The four primitives: Agent, Task, Crew, Process
  3. The Agent in depth: role, goal, backstory, tools, llm
  4. The Task in depth: description, expected_output, agent, context
  5. The sequential process: the mechanism
  6. Context passing and task chaining
  7. Input interpolation: one crew, many runs
  8. The hierarchical process: a manager that delegates and synthesizes
  9. Sequential vs hierarchical: when each, and the manager cost
  10. Tools: how agents act on the world
  11. Flows: event-driven orchestration, distinct from Crews
  12. Flow decorators: start, listen, router, or_ and and_
  13. Flow state, persistence, human-in-the-loop, and Crews inside Flows
  14. CrewAI vs LangGraph vs AutoGen
  15. Production concerns: determinism, cost, observability
  16. Common misconceptions
  17. Lab walkthrough
  18. Success criteria
  19. Interview Q&A
  20. 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:

PrimitiveWhat it isKey fields
Agenta persona wrapped around an LLMrole, goal, backstory, tools, llm, allow_delegation
Taska unit of work for an agentdescription, expected_output, agent, context, tools, output_pydantic
Crewa team of agents executing tasksagents, tasks, process, manager_llm
Processhow the crew runs its taskssequential 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=True additionally 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 from kickoff inputs.
  • 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. Under sequential you set this explicitly; under hierarchical you 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:

  1. Tasks run in the order listed on the crew. Task N does not start until task N-1 finishes.
  2. 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.
  3. 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 valueThe task receivesUse it for
None (unset, the default)the previous task's outputa straight pipeline (research → write → edit)
[t1, t3] (a list of tasks)exactly those tasks' outputs, concatenateda fan-in (combine two specific upstream results)
[] (empty list)nothingan 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:

  1. 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).
  2. Execution — the chosen worker performs the task, seeing the work done so far.
  3. 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:

sequentialhierarchical
task → agent assignmentyou set it (agent=)the manager decides at runtime
who controls order/flowthe listed order (no LLM)a manager LLM
LLM callsN (one per task)N worker calls + N+1 manager calls
determinismhigh (given the model)lower — flow depends on manager judgment
failure surfaceeach taskeach task + the manager (single point)
debuggabilitytrivial (fixed pipeline)you must read the delegation trail
best whenthe path is known and fixedyou 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 @start method runs when you call kickoff. A flow can have several (parallel entry points).
  • @listen(trigger) — the method runs when trigger completes, and receives the trigger's return value (if the method declares a parameter). trigger is usually another method (referenced by identity in the class body) or a router label.
  • @router(trigger) — runs after trigger, 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 of a, b completes (an "either path" join).
  • and_(a, b) — a trigger that fires only when all of a, b complete (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:

  • Unstructuredself.state is a plain dict, which CrewAI auto-stamps with an id. Flexible, schemaless, easy to typo a key and never notice.
  • Structuredself.state is a typed model. In real CrewAI that is a Pydantic BaseModel (declared class MyFlow(Flow[MyState])); it too gets an id. You get validation, autocomplete, and a schema every step agrees on. The lab uses a stdlib @dataclass in 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:

FrameworkCore abstractionReach for it whenThe cost
CrewAIroles + crews + process (and Flows for orchestration)you want a role-based team stood up fast; role-playing collaboration reads naturallythe process hides control flow; hierarchical adds a manager LLM; less explicit state control than a graph
LangGraphan 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 controlmore upfront wiring; you draw the state machine yourself (Phase 18)
AutoGenconversation between agents (a chat among agents, e.g. GroupChat)the task is naturally a dialogue/negotiation among agents, or human-in-the-loop chatconversation 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_usage from 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 Delegation records and Flow.completed list 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.
  • "context is 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 set context explicitly — 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 @router passes data to the branch." A router routes — it emits a label that selects which @listen fires. Data flows through self.state and 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.

  1. Lab 01 — Sequential Crew. Build interpolate, then Agent (render_prompt + execute), Task, and Crew.kickoff for the sequential process. The crux is _build_context — the three-way None / [...] / [] switch of §6. Prove order, context feed-forward vs explicit fan-in vs isolation, interpolation reaching the prompt, and determinism.
  2. Lab 02 — Hierarchical Process. Build Manager (allocate, synthesize) and Crew.kickoff for the hierarchical process. Watch the two invariants: an unknown-worker delegation is a hard error, and manager_calls == len(tasks) + 1. Contrast it with lab 01's sequential engine in your head as you go.
  3. Lab 03 — Flows. Build the trigger Condition (or_/and_), the @start/@listen/@router decorators, and the round-based Flow.kickoff event loop, plus structured (dataclass) vs unstructured (dict) state and @persist. Prove a router branch, an and_ 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 Process is (the execution model).
  • You can state the three context modes and what each produces, and name the forward- reference error.
  • You can explain, from the loop, why sequential is deterministic given the model.
  • You can compute the hierarchical process's LLM-call overhead (N+1 manager calls) and say when it is worth paying.
  • You can wire a @router branch and an and_ fan-in by hand, and explain what a @listen method receives.
  • You can contrast structured vs unstructured Flow state and say what @persist stores and where (SQLite, keyed by id).
  • You can give the one-line "reach for it when…" for CrewAI vs LangGraph vs AutoGen.
  • All three labs pass under both lab and solution (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.