Skip to content

Custom agents

Load .agent.md definitions through custom_agents to test custom agent dispatch. A custom agent is a definition, not inherently a subagent. result.subagent_invocations records actual runtime invocations.

Load one custom agent

For example, create .github/agents/writer.agent.md:

---
name: writer
description: Write requested JSON artifacts.
---

Write the requested JSON file in the working directory.
Use exactly the fields and values requested.

Then load it:

from pytest_skill_engineering import load_custom_agent
from pytest_skill_engineering.copilot import CopilotEval

writer = load_custom_agent(".github/agents/writer.agent.md")
agent = CopilotEval(
    name="orchestrator",
    model="gpt-5.6-sol",
    instructions="Delegate JSON file-writing tasks to the writer custom agent.",
    custom_agents=[writer],
)

Standalone, plugin, and project loaders retain declared names and supported settings: model, tools, reasoning effort, inference settings, skills, and mcp-servers. Missing names use the filename. An explicit empty tool list stays empty; duplicate resolved names are errors.

Assert on runtime custom agent dispatch

With the definition above, this test checks both dispatch and the artifact:

import json
from dataclasses import replace


async def test_dispatches_to_writer(copilot_eval, tmp_path):
    config = replace(agent, working_directory=str(tmp_path))
    result = await copilot_eval(
        config, 'Ask writer to create answer.json containing exactly {"total": 5}.'
    )

    assert result.success, result.error
    assert any(
        invocation.name == "writer" and invocation.status == "completed"
        for invocation in result.subagent_invocations
    )
    assert json.loads((tmp_path / "answer.json").read_text(encoding="utf-8")) == {"total": 5}

An observed invocation alone does not prove task correctness. For a review agent, for example, verify the expected findings against a known fixture rather than asserting that a review was dispatched.

Invocation statuses include selected, started, completed, and failed. Framework-owned invocations retain a child result with its own conversation, tools, usage, and capture errors. See Child execution evidence.

Each nested invocation gets the remaining registry without its own definition. The registry shrinks along the dispatch path, preventing dispatch back to itself or an ancestor. Siblings can use definitions available in their parent's registry.

Load a directory of custom agents

from pytest_skill_engineering import load_custom_agents

specialists = load_custom_agents(
    ".github/agents",
    exclude={"orchestrator"},
    overrides={"writer": {"model": "gpt-5.6-sol"}},
)

The directory loader reads immediate .agent.md files. Include/exclude filters and per-agent overrides use resolved names, including declared names.

Compare custom agent variants

Parametrize tests over definition files, load each with load_custom_agent(), and apply the same dispatch and output checks. Keep the task, parent model, tools, state, and limits fixed. A variant's declared child model or tools are part of the configuration being compared.

Use Comparing configurations for separate pytest outcomes or paired ab_run evidence. Neither session completion nor dispatch events automatically assess the quality of the agent's work.