Skip to content

researcher

agentic_ml.research.researcher

The generic research orchestrator.

:class:Researcher ties a :class:~agentic_ml.core.task.Task to a Strands model and drives the experimentation loop, then exports the winning trial as a light :class:~agentic_ml.core.model.AgenticModel.

Strands is imported lazily inside :meth:research, so importing this module (and therefore a concrete researcher such as TabularRegressionResearcher) does not require the research extra. Only actually running :meth:research does.

Researcher

Researcher(task: Task, model: object | None = None)

Run agent-driven research for a given task and export the best model.

Attributes:

Name Type Description
task

The :class:~agentic_ml.core.task.Task defining the problem being solved (prompt sections, metrics, partitioning and evaluation scheme).

model

The Strands model instance driving the agent, or None if this researcher was built without one — in that case :meth:research raises a :class:ValueError as soon as it is called.

agent object | None

The Strands agent built from task/model, created lazily the first time :meth:research runs; None before that.

context ResearchContext

The :class:~agentic_ml.core.engine.ResearchContext from the last :meth:research call; raises :class:RuntimeError if accessed before research has run.

Source code in src/agentic_ml/research/researcher.py
71
72
73
74
75
def __init__(self, task: Task, model: object | None = None) -> None:
    self.task = task
    self.model = model
    self._ctx: ResearchContext | None = None
    self.agent: object | None = None

context property

context: ResearchContext

The :class:~agentic_ml.core.engine.ResearchContext from the last research run.

research

research(
    data: DataFrame,
    schema: DatasetSchema,
    *,
    metrics: list[str] | None = None,
    partitions: list[Partition] | None = None,
    n_splits: int = 5,
    seed: int = 0,
    tolerance: float = 1e-06,
    timeout: float = 120.0,
    iterations: int = 10,
    ideas: str | None = None,
    research_dir: str | None = None,
    sleep: float = 0.0,
    new_session: bool = True,
    agent_config: AgentConfig | None = None,
) -> ResearchContext

Drive the research loop and return the resulting context.

Parameters:

Name Type Description Default
data DataFrame

The tabular dataset, including the target column.

required
schema DatasetSchema

Variable descriptions and the target column name.

required
metrics list[str] | None

Metric names to track; defaults to the task's metrics.

None
partitions list[Partition] | None

Optional explicit (train_idx, val_idx) splits; otherwise built by the task (K-Fold by default).

None
iterations int

Hard cap on the number of trials create_trial will actually create, enforced by create_trial_action (not just suggested to the agent via the kickoff prompt, which also mentions this number so the agent can plan ahead).

10
ideas str | None

Natural-language ideas the user wants the agent to try.

None
research_dir str | None

Where to store trials and the leaderboard. Defaults to a timestamped directory under ./agentic_ml_runs.

None
sleep float

How long to wait between iterations, in seconds. This is useful to avoid RateLimit errors with the LLM provider.

0.0
new_session bool

If True, clears any previous session cache under research_dir and starts a brand-new agent. If False, rebuilds the agent from scratch against this call's fresh context but restores its conversation from the on-disk session cache (whether written by an agent still alive on this instance or by an earlier process that has since restarted). Only meaningful when calling :meth:research again with the same research_dir as a previous call.

True
agent_config AgentConfig | None

Tunes the Strands agent's conversation manager and context offloader (see :class:~agentic_ml.research.agent.AgentConfig); defaults to AgentConfig() (all field defaults) when not passed.

None
Source code in src/agentic_ml/research/researcher.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def research(
    self,
    data: pd.DataFrame,
    schema: DatasetSchema,
    *,
    metrics: list[str] | None = None,
    partitions: list[Partition] | None = None,
    n_splits: int = 5,
    seed: int = 0,
    tolerance: float = 1e-6,
    timeout: float = 120.0,
    iterations: int = 10,
    ideas: str | None = None,
    research_dir: str | None = None,
    sleep: float = 0.0,
    new_session: bool = True,
    agent_config: AgentConfig | None = None,
) -> ResearchContext:
    """Drive the research loop and return the resulting context.

    Args:
        data: The tabular dataset, including the target column.
        schema: Variable descriptions and the target column name.
        metrics: Metric names to track; defaults to the task's metrics.
        partitions: Optional explicit (train_idx, val_idx) splits; otherwise built by the
            task (K-Fold by default).
        iterations: Hard cap on the number of trials ``create_trial`` will actually create,
            enforced by ``create_trial_action`` (not just suggested to the agent via the
            kickoff prompt, which also mentions this number so the agent can plan ahead).
        ideas: Natural-language ideas the user wants the agent to try.
        research_dir: Where to store trials and the leaderboard. Defaults to a
            timestamped directory under ``./agentic_ml_runs``.
        sleep: How long to wait between iterations, in seconds. This is useful
            to avoid RateLimit errors with the LLM provider.
        new_session: If ``True``, clears any previous session cache under ``research_dir``
            and starts a brand-new agent. If ``False``, rebuilds the agent from scratch
            against this call's fresh context but restores its conversation from the
            on-disk session cache (whether written by an agent still alive on this instance
            or by an earlier process that has since restarted). Only meaningful when
            calling :meth:`research` again with the *same* ``research_dir`` as a previous
            call.
        agent_config: Tunes the Strands agent's conversation manager and context offloader
            (see :class:`~agentic_ml.research.agent.AgentConfig`); defaults to
            ``AgentConfig()`` (all field defaults) when not passed.
    """
    if self.model is None:
        raise ValueError(
            "a Strands model instance is required to run research; pass one via "
            "Researcher(model=...) (install the 'research' extra)"
        )

    from agentic_ml.research.agent import AgentConfig as _AgentConfig

    agent_config = agent_config or _AgentConfig()

    chosen_metrics = metrics or self.task.default_metrics()
    primary = self.task.primary_metric()
    if primary not in chosen_metrics:
        chosen_metrics = [primary, *chosen_metrics]

    config = ResearchConfig(
        metrics=chosen_metrics,
        primary_metric=primary,
        maximize=self.task.maximize(),
        n_splits=n_splits,
        seed=seed,
        tolerance=tolerance,
        timeout=timeout,
        iterations=iterations,
        ideas=ideas,
        partitions=partitions,
        sleep=sleep,
    )

    if research_dir is None:
        stamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
        research_dir = f"./agentic_ml_runs/{self.task.name}_{stamp}"

    ctx = setup_research(self.task, data, schema, config, research_dir, agent_config)
    self._ctx = ctx

    self._load_session(new_session)
    error = self._kickoff_research_agent(config)
    if error is not None:
        raise error

    return ctx

export_model

export_model(trial: str | None = None) -> AgenticModel

Export a trial (the best by default) as a light production model.

Source code in src/agentic_ml/research/researcher.py
219
220
221
222
223
224
225
def export_model(self, trial: str | None = None) -> AgenticModel:
    """Export a trial (the best by default) as a light production model."""
    ctx = self.context
    trial_id = trial or select_best(ctx)
    if trial_id is None:
        raise RuntimeError("no successful trial to export")
    return AgenticModel.from_trial(ctx.workspace.trial_dir(trial_id))