Skip to content

Researcher

Researcher gives a Pydantic AI agent a compact stack for broad web research with source-backed answers. It is a regular combined capability made from the capabilities below, so you can use it as-is or take it apart.

While Pydantic AI Harness is on 0.x releases, the API may change between minor releases; when it does, deprecation warnings and release-note migration guidance tell you (or your agent) exactly how to upgrade. See the version policy.

Researcher combines research instructions with four capabilities. WebSearch and WebFetch use the model provider's native search and fetch, or duckduckgo_search and web_fetch where the model has none. SubAgents adds delegate_task, which hands a sub-question to a 'researcher' sub-agent with its own search, fetch and output limits. ToolOutputLimits spills oversized tool results to the run workspace, where read_tool_result reads them.

Usage

Install the local search and fetch fallbacks (DuckDuckGo search, page-to-Markdown fetching):

Terminal
pip install "pydantic-ai-harness[researcher]"

Then ask it a question:

from pydantic_ai import Agent
from pydantic_ai.capabilities import LocalWorkspace
from pydantic_ai_harness import Researcher

agent = Agent('openai:gpt-6.1-sol', capabilities=[LocalWorkspace('.'), Researcher()])

result = agent.run_sync('What changed in the last three major releases of Django?')
print(result.output)

The same agent works with every Pydantic AI interface: agent.to_cli_sync() for terminal chat, agent.to_web() for a browser chat UI. It works under durable execution too: pass Researcher() next to TemporalDurability(), DBOSDurability(), or PrefectDurability() when you construct the agent, and keep the workspace attached for its tool-result spills.

Or skip the file entirely and run the exported researcher_agent with clai (the Pydantic AI CLI), via uvx:

Terminal
uvx --with 'pydantic-ai-harness[researcher]' clai -a pydantic_ai_harness.researcher:researcher_agent -m openai:gpt-6.1-sol

What’s inside

It combines these capabilities, in order:

  • Concise default research instructions: see Instructions below
  • Core WebSearch(local=True): the provider’s native web search when the model supports it, with a local DuckDuckGo fallback when it doesn’t
  • Core WebFetch(local=True): read the pages behind the results, native where supported with a local fallback, so claims can be checked against their sources
  • SubAgents: delegation, with a focused web researcher sub-agent by default
  • ToolOutputLimits: bounds how much context any single tool result can consume, spilling oversized results to files in the run’s workspace

Pass subagents=[] to disable delegation, or supply your own SubAgent entries.

Oversized tool results are stored in the run’s workspace, under .pydantic-ai-harness/tool-output/ (git-ignored), so a run without one fails at its start. LocalWorkspace('.') keeps them in the current directory and a sandbox capability keeps them in the sandbox. To run without a workspace, pass Researcher(store=LocalFileStore()); spills then stay on the machine running the agent, including for its default delegate. A custom delegate needs its own store configured.

Instructions

Researcher comes with short default research instructions (DEFAULT_RESEARCHER_INSTRUCTIONS, written out in full in the blown-out equivalent below). Pass instructions='...' to replace them with your own, or instructions=None to get only the abilities, with no default instructions at all.

Extending it

  • Research in a specific format: give the agent a typed output_type: a Pydantic model of findings, each with its source link, and the researcher returns structured data instead of prose.
  • Exa search: swap in Exa Search as the search backend.
  • Fan out: add Dynamic Workflow so the agent can spawn typed researcher sub-agents in parallel and combine their structured results.

Blown-out equivalent

from pydantic_ai import Agent
from pydantic_ai.capabilities import LocalWorkspace, WebFetch, WebSearch
from pydantic_ai_harness import SubAgent, SubAgents, ToolOutputLimits

instructions = """\
Search broadly before drawing conclusions.
Read the sources that support each important claim.
Prefer primary and authoritative sources.
Cite every factual claim with a direct source link.
Distinguish sourced facts from your own inference.
"""

sub_researcher = SubAgent(
    Agent(
        name='researcher',
        description='Research a focused sub-question on the web and report back with findings and source links',
        capabilities=[WebSearch(local=True), WebFetch(local=True), ToolOutputLimits()],
    )
)

agent = Agent(
    'openai:gpt-6.1-sol',
    instructions=instructions,
    capabilities=[
        LocalWorkspace('.'),  # where oversized tool results are spilled
        WebSearch(local=True),  # native provider search, DuckDuckGo fallback on models without it
        WebFetch(local=True),  # read the pages behind the results, native or local
        SubAgents(agents=[sub_researcher], agent_folders=None),
        ToolOutputLimits(),  # spills oversized results to the workspace
    ],
)

See the source.

API reference

Researcher

Bases: CombinedCapability[AgentDepsT]

Web research with search, page fetching, a research delegate, and tool-output limits.

Oversized tool results are spilled to files in the run’s workspace, so a run needs one: attach LocalWorkspace('.') or a sandbox capability. A run without a workspace fails at its start unless store= provides a non-workspace overflow store such as LocalFileStore().

Constructor Parameters

instructions : str | None Default: DEFAULT_RESEARCHER_INSTRUCTIONS

Replaces DEFAULT_RESEARCHER_INSTRUCTIONS; None adds no instructions.

subagents : Sequence[SubAgent[AgentDepsT]] | None Default: None

Delegates for SubAgents. None uses a focused web researcher delegate; an empty sequence leaves delegation out.

store : OverflowStore | WorkspaceStore | None Default: None

Where oversized tool results are spilled, for Researcher and its default delegate. None uses the run’s workspace.

researcher_agent

Model-less research agent for CLIs that load module:variable targets.

Default: Agent[object](name='researcher', capabilities=_capabilities)