How do CrewAI's kickoff, kickoff_async and kickoff_for_each differ when running a Crew?
answer
- one run, or many runs
- await does not mean parallel
- each item gets a fresh copy
- results come back in input order
- braces get filled from the dict
basics
~20 skickoff(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.
solid answer
~50 s`crew.kickoff(inputs={...})` is the blocking entry point: it interpolates the `inputs` dict into the `{placeholder}` slots in your task descriptions and agent definitions, executes the crew under its configured process, and returns a single `CrewOutput`. `crew.kickoff_async(inputs={...})` is the same single run exposed as a coroutine, so you can `await` it inside an async application without blocking the event loop — useful when a web handler triggers a crew. `crew.kickoff_for_each(inputs=[{...}, {...}])` is the batch form: it runs the crew once per dict in the list and returns a list of `CrewOutput` in input order. Each run gets its own copy of the crew, so state does not bleed between items. There is also an async batch variant that runs those iterations concurrently — which is where you get real throughput, and also where you hit provider rate limits, so pair it with the crew's `max_rpm`.
code
python · 14 lines# one run
result = crew.kickoff(inputs={"topic": "vector databases"})
print(result.raw)
# one run, awaitable
result = await crew.kickoff_async(inputs={"topic": "vector databases"})
# many runs, one CrewOutput per input
results = crew.kickoff_for_each(inputs=[
{"topic": "vector databases"},
{"topic": "embedding models"},
])
for r in results:
print(r.raw)go deeper
Be able to say that kickoff runs the crew once with an inputs dict, kickoff_async is the awaitable version of that same run, and kickoff_for_each runs it once per input and hands back a list of results.
Explain interpolation: the inputs dict fills the curly-brace placeholders in task descriptions and agent fields before any prompt is sent. Mention that the batch form copies the crew per item so state does not leak.
Discuss throughput and blast radius: only the async batch form overlaps runs, and that is when rate limits and per-item failure handling become real. Tie batch size to cost, since every item pays the full crew price.
Frame kickoff inputs as an untrusted-data boundary — they are substituted straight into prompts — and set the org's policy for batch execution: concurrency ceilings, per-item failure semantics, and cost approval before a fan-out job runs.
## The four entry points A CrewAI `Crew` exposes a small family of run methods, and interviewers ask about them because choosing the wrong one is how people accidentally serialise a batch job or leak state between customers. - `kickoff(inputs: dict | None) -> CrewOutput` — one run, blocking. - `kickoff_async(inputs: dict | None) -> CrewOutput` — one run, awaitable. - `kickoff_for_each(inputs: list[dict]) -> list[CrewOutput]` — N runs, one per dict, sequentially. - `kickoff_for_each_async(inputs: list[dict]) -> list[CrewOutput]` — N runs, concurrently. ## What inputs actually does The `inputs` dict is not passed to the agents as a message. CrewAI interpolates it into the curly-brace placeholders in your configuration: a task whose description reads `"Research {topic} for the {audience} audience"` gets those slots filled before the agent ever sees a prompt, and the same substitution applies to agent role, goal and backstory text. This is what makes one crew definition reusable across many inputs. Two consequences worth naming: 1. A placeholder with no matching key is a configuration bug that shows up as literal braces in the prompt, so keep the keys and the templates in sync. 2. Because interpolation is plain text substitution into prompts, untrusted input lands directly in the instruction stream. Validate anything user-supplied before it becomes a kickoff input. ## The batch form and crew copies `kickoff_for_each` exists because re-running the same crew object N times by hand is subtly wrong: agents accumulate state across a run. The batch method takes a copy of the crew for each iteration, so item 7's conversation does not contaminate item 8's. It then returns a list of `CrewOutput` aligned with the input list, so `results[i]` corresponds to `inputs[i]`. That isolation is also why the batch form is not free: each iteration pays the full crew cost. A 50-item batch over a four-task crew is 200 task executions, and if the crew is hierarchical, considerably more LLM calls than that. ## Sync, async and throughput `kickoff_async` does not make a single crew run faster — the tasks inside it still execute under the crew's process. What it buys is not blocking the caller's event loop, which matters when a crew is triggered from an async web framework or when you want to run a crew alongside other I/O. The async batch variant is the one that actually increases throughput, by overlapping independent crew runs. That is exactly when you meet provider rate limits, so it pairs naturally with the crew's `max_rpm` setting, and with the knowledge that partial failure is now possible: one item's run can fail while others succeed, and you need to decide whether the batch fails or reports per-item results. ## Choosing between them - Script, notebook, CLI: `kickoff`. - Async service handler: `kickoff_async`. - Offline batch over a list of records where cost is bounded and order matters: `kickoff_for_each`. - Same batch, but latency matters and you can absorb the rate-limit pressure: the async batch variant. ## Common mistakes The mistake interviewers listen for is treating `kickoff_async` as a parallelism feature — it is a concurrency-friendly wrapper around a single run, not a fan-out. The second is reusing one crew object in a hand-rolled loop and being surprised when later iterations reference earlier ones, which is precisely the problem `kickoff_for_each` solves with its per-iteration copy. The third is forgetting that batch results are `CrewOutput` objects, not strings, so you still go through `.raw` (or the structured fields) to extract content. ## Version note Describes the CrewAI 0.x `Crew` run API.
- Why does kickoff_for_each copy the crew for each input instead of reusing one instance?Because agents accumulate state within a run — conversation history, tool results, memory writes. Reusing a single instance across items would let item N see item N-1's context, producing answers that are subtly contaminated and impossible to reproduce in isolation. A per-iteration copy makes each result a function of its own input only, which is what a batch job needs.
- Does awaiting kickoff_async make the tasks inside the crew run in parallel?No. The crew still executes under its configured process, so a sequential crew is still one task at a time. What the coroutine buys you is not blocking the caller's event loop, so an async service can serve other requests while the crew works. Real overlap of whole runs comes from the async batch variant, not from awaiting one kickoff.
- What breaks first when you run a large batch concurrently?Provider rate limits, usually as 429s partway through the batch, and cost — every item pays the full crew price. Set the crew's `max_rpm` to keep request rate under the provider ceiling, decide up front whether a failed item fails the batch or is reported individually, and size a pilot batch before running thousands.
saying these in an interview costs you the question
- Thinks kickoff_async runs the crew's tasks in parallel
- Expects kickoff_for_each to return one merged CrewOutput
- Reuses one crew instance in a manual loop and expects isolation
- Believes inputs are sent to agents as a chat message rather than interpolated
- Assumes the batch form costs about the same as a single run