skip to content

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

level: middleimportance: should knowfreq 46%

answer

  1. Prompt text out of code, objects in code
  2. The YAML key is a lookup name
  3. One decorated method per agent
  4. Decorated methods are collected automatically
  5. Braces resolve from the run's inputs

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.

solid answer

~40 s

A scaffolded CrewAI project keeps prompt text out of code. `config/agents.yaml` maps an agent name to its `role`, `goal` and `backstory`; the class decorated with `@CrewBase` declares `agents_config` pointing at that file; and each method decorated with `@agent` returns an `Agent` constructed with `config=self.agents_config["researcher"]`, adding in Python whatever YAML cannot express — the `llm`, the `tools` list, a `step_callback`. The decorator also registers the method, so the crew can collect the agents without you listing them by hand. `{placeholder}` values inside the YAML strings interpolate from the `inputs` dictionary passed when the crew is kicked off, which is what makes one definition serve many runs. The tradeoff is that YAML keys are unchecked: a typo'd agent name or a placeholder with no matching input surfaces at run time, not at import.

code

yaml · 8 lines
yaml
researcher:
  role: >
    {topic} Senior Research Analyst
  goal: >
    Find and verify the three most significant recent developments in {topic}
  backstory: >
    You check every claim against a primary source and report when none exists.
  allow_delegation: false

go deeper

for a junior

Know that scaffolded CrewAI projects keep role, goal and backstory in config/agents.yaml and build the agent in a decorated method with Agent(config=self.agents_config["name"]).

for a middle

Explain the split cleanly — serialisable prompt text in YAML, live objects in Python — that @agent registers the method so the crew collects it, and that placeholders interpolate from the kickoff inputs.

for a senior

Show that you treat the YAML as unchecked configuration: a test that constructs the crew and asserts each agent's fields, validated inputs before kickoff, and a clear rule for which knobs live in config versus code.

for a principal

Own prompt configuration as an interface: who is allowed to change YAML, how those changes are reviewed and evaluated before release, and whether the indirection is worth it for the crews in question.

## Why the split exists Agent definitions are two very different kinds of thing wearing one constructor. `role`, `goal` and `backstory` are prose that changes often, benefits from review and reads badly inside Python string literals. `llm`, `tools` and callbacks are live objects that cannot be serialised into a config file at all. The scaffolded CrewAI project layout splits them along exactly that line: text in YAML, objects in Python. ## The YAML side `config/agents.yaml` is a mapping from an agent name to its fields: ``` researcher: role: > {topic} Senior Research Analyst goal: > Find and verify the three most significant recent developments in {topic} backstory: > You check claims against primary sources and say so when none exist. ``` The top-level key (`researcher`) is the lookup name used from code — it is not the role. YAML block scalars (`>`) keep long prose readable without escaping. Only serialisable fields belong here: the three prompt strings, and simple flags like `allow_delegation`, `max_iter` or `verbose` if you prefer them in config. ## The Python side The crew class is decorated with `@CrewBase` and declares where its config lives; each agent is a method decorated with `@agent`: `@agent` `def researcher(self) -> Agent:` ` return Agent(config=self.agents_config["researcher"], tools=[search_tool], verbose=True)` `Agent(config=...)` unpacks the YAML mapping into the constructor's fields; anything you also pass explicitly is set alongside it. This is where the objects go: the `LLM` instance, the tool list, a `step_callback`. ## What the decorator buys `@agent` is not decoration. It marks the method as producing one of the crew's agents so that the `@CrewBase` machinery can collect them, which is why the crew definition can pass a collected list of agents rather than naming each method. The practical effect is that adding an agent is one YAML block plus one decorated method — no third place to update, and no chance of defining an agent that the crew silently never receives because you forgot to add it to a list. ## Interpolation Placeholders written as `{topic}` inside the YAML strings are filled from the `inputs` dictionary supplied when the crew is kicked off. That is what turns a static persona into a per-run one, and it means the set of placeholders across your YAML files is effectively the crew's input contract. A placeholder with no matching key fails at run time. The corollary is worth stating out loud in an interview: braces in YAML prompt text are not inert, so literal braces in an example you want the model to see need care. ## The tradeoffs What you gain: prompt text becomes reviewable in a diff by someone who does not read Python, non-engineers can propose wording changes, and the same code can be pointed at a different config. Prompt iteration stops being a code change in spirit even when it is one in fact. What you pay: the YAML has no schema enforcement in your editor. A misspelled key inside an agent block is not applied and does not warn; a misspelled top-level name raises only when the lookup runs; a placeholder typo raises only at kickoff. Type checkers cannot see through the config dictionary into constructor fields either, so the usual advice is to have at least one test that constructs the crew and asserts each agent's role is what you expect — cheap insurance that turns a run-time surprise into a build failure. ## When to skip it For a single-file experiment or a crew built dynamically from user input, plain Python `Agent(...)` calls are clearer than a config indirection with one consumer. The YAML layout earns its keep when prompts are long, revised often, or edited by people who should not be in the code — which describes most crews that reach production. ## Interview framing Describe the split (text versus objects), name the three moving parts (`agents.yaml`, `agents_config` on the `@CrewBase` class, the `@agent` method that constructs the agent from the entry), and mention interpolation from kickoff inputs. Then be honest about the cost: unchecked keys, run-time-only errors, and the test you write to compensate.

  • Which Agent fields cannot live in agents.yaml, and why?
    Anything that is a live object rather than data: the `llm` instance, the `tools` list, and callbacks such as `step_callback`. YAML can only carry serialisable values, so those are passed in the `@agent` method alongside `config=`. Simple scalars like `allow_delegation`, `max_iter` and `verbose` can go either way — put them wherever the people who tune them will look.
  • A typo in an agents.yaml field name silently does nothing. How do you catch that?
    With a test that builds the crew and asserts the agents' fields — role text, tool count, delegation flag — since neither the editor nor a type checker sees through the config mapping. Constructing the crew in CI also catches a misspelled top-level key, which otherwise fails only when that lookup runs at request time.
  • What is the relationship between placeholders in agents.yaml and the crew's inputs?
    They are the same contract seen from two sides: every `{placeholder}` across the YAML must have a matching key in the `inputs` dictionary supplied at kickoff, or the run fails when interpolation happens. Treat the union of placeholders as the crew's public input schema, and validate the inputs before kickoff rather than discovering a missing key mid-run.

saying these in an interview costs you the question

  • Thinks the YAML top-level key is the agent's role
  • Tries to put tools or an LLM object into YAML
  • Assumes a misspelled YAML field is reported at import
  • Believes placeholders read from environment variables
  • Defines an agent method without the decorator and expects it to run

context