> ## Documentation Index
> Fetch the complete documentation index at: https://docs.expectedparrot.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents

> Define and vary simulated respondent traits and instructions.

An EDSL `Agent` describes the perspective from which a model should answer: for example, a first-time user, an experienced researcher, or a reviewer applying a codebook. Its traits supply background information and its instructions say how to respond. The [Model](https://docs.expectedparrot.com/en/latest/language_models.md) generates the answers; the Agent supplies the role or profile.

Use agents to compare responses across defined perspectives or to give a consistent reviewing instruction across questions. You can omit a custom agent when the task only needs a question and source material. An EDSL Agent is distinct from a coding assistant or the [Research Agent](https://docs.expectedparrot.com/en/latest/research_agent.md).

## Define and vary profiles

The example combines two experience levels with two roles to create four profiles. Each evaluates both a short message and a more explicit message, answering the same clarity question:

```python theme={null}
from edsl import Agent, AgentList, QuestionMultipleChoice, Scenario, ScenarioList

agents = AgentList([
    Agent(traits={"experience": experience, "role": role},
          instruction="Answer using only the supplied profile and message.")
    for experience in ["new user", "experienced user"]
    for role in ["individual contributor", "team lead"]
])
messages = ScenarioList([
    Scenario({"condition": "short", "message": "Export your report."}),
    Scenario({"condition": "explicit", "message": "Download your report as a CSV file."}),
])
question = QuestionMultipleChoice(
    question_name="clarity",
    question_text="How clear is this instruction: {{ scenario.message }}?",
    question_options=["Clear", "Unclear", "Unsure"],
)
job = question.by(agents).by(messages)
# Four profiles crossed with two messages gives eight interviews per model.
```

Four profiles times two messages gives eight interviews per model and iteration. The results retain `agent.experience` and `agent.role`, so you can compare message ratings within each profile. [Experimental conditions](https://docs.expectedparrot.com/en/latest/guides/experimental_conditions.md) shows how to run and analyze this design. These are responses generated from the specified profiles, rather than observations collected from people with those characteristics.

## Traits, instructions, and names

`traits` contains profile data. `instruction` describes how the agent should respond. An optional `name` identifies the agent in results; it does not automatically become a demographic trait. Use [Prompts](https://docs.expectedparrot.com/en/latest/prompts.md) to see what is actually sent to the model.

For data imported from a CSV or DataFrame, see [Importing data](https://docs.expectedparrot.com/en/latest/guides/importing_data.md). Inspect field names, missing values, and category definitions before using those rows as profiles.

## Advanced: dynamic traits

A dynamic traits function supplies traits based on the question being asked. This is useful when different questions should receive different parts of a profile, rather than sending the whole profile every time. The example below keeps a reviewer role and adds the current question's name:

```python theme={null}
from edsl import Agent

def traits_for_question(question):
    return {"role": "reviewer", "current_question": question.question_name}

agent = Agent(dynamic_traits_function=traits_for_question)
```

Inspect prompts for every affected question and preserve the function with the study.

## Common methods

| Call | Arguments and result |
| - | - |
| `Agent(traits=..., instruction=..., name=...)` | Create a profile from a trait dictionary, response instructions, and an optional identifying name. |
| `AgentList([agent, ...])` | Collect profiles to apply to a study. |
| `agent.add_trait("role", "reviewer")` | Return a new Agent with the added or replaced trait. |
| `agent.remove_trait("role")` | Return a new Agent without that trait. |
| `agents.select("role", "experience")` | Return an AgentList containing only the named traits. |
| `agents.filter("experience == 'new user'")` | Return an AgentList with profiles matching the expression. |

Using the profiles above, select new users before adding them to a job:

```python theme={null}
new_users = agents.filter("experience == 'new user'")
new_users.select("experience", "role")
# In a script, append .print() to display the selected profiles.
```

## Revise profiles and choose a subset

Trait transformations return new Agents, so assign the result:

```python theme={null}
from edsl import Agent, AgentList

profile = Agent(traits={"role": "reviewer", "experience": "new"})
experienced = profile.update_trait("experience", "experienced")
profiles = AgentList([profile, experienced]).with_names("experience", remove_traits=False)
profiles.set_instruction("Answer from the perspective of the described person.")
profiles.to_scenario_list()
```

`update_trait()` requires the trait to exist; `add_trait()` can introduce a new trait. `agent.rename({"role": "occupation"})` changes trait names, and `agent.translate_traits({"experience": {"new": "new user"}})` recodes values. `agent.select(...)` and `agent.drop(...)` keep or remove traits. `agent.prompt()` shows the traits presentation text; use `job.prompts()` to inspect the complete model prompt.

| Task | AgentList methods |
| - | - |
| Filter and sample profiles | `filter(expression)`, `sample(n=2, seed="pilot")`, `shuffle(seed="pilot")`, and `split(frac_left=0.5, seed=42)`. |
| Transform traits | `select(...)`, `drop(...)`, `rename("old", "new")`, `add_trait("condition", values)`, and `remove_trait("condition")`. Supply one value per agent to `add_trait()`. |
| Clean imported traits | `filter_na(["role"])` removes missing required traits; `numberify()` converts numeric strings. |
| Set names | `with_names("profile_id", remove_traits=False)` returns a named copy. `give_names(...)` modifies the list's agents; both remove naming traits by default unless told otherwise. |
| Set shared instructions | `set_instruction(text)` changes every agent; `set_traits_presentation_template(template)` changes how traits are presented to models. These modify agents in place. |
| Select traits by question | `set_dynamic_traits_from_question_map({"q1": ["role"], "q2": ["experience"]})` limits the traits supplied for each question. |
| Summarize and export | `tally("role")`, `sql(query)`, `to_dataset()`, `to_scenario_list()`, and the [table export methods](https://docs.expectedparrot.com/en/latest/dataset.md#export-and-convert-tables). |

`AgentList.from_list("role", values)` creates one agent per value. `AgentList.from_results(results, question_names=["role"])` turns selected answers into traits for a subsequent study; `Agent.from_result(result)` does this for one interview.

## Supply deterministic answers

For a simulation with a known response rule, attach a Python function to an Agent:

```python theme={null}
from edsl import Agent

def answer_directly(self, question, scenario):
    return "Yes" if self.traits["uses_service"] else "No"

agent = Agent(traits={"uses_service": True})
agent.add_direct_question_answering_method(answer_directly)
# Use with a question whose allowed answers are Yes and No.
# agent.remove_direct_question_answering_method() restores model-based answering.
```

The function supplies the answer without an LLM response. Its return value must suit the question type. Preserve the function with your study so the rule can be inspected and reproduced.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.