# Build Your First GitHub Copilot Custom Agent: A Practical LoadRunner Example

> Source: <https://dev.to/kavin_arvind_8a1adbd39efd/build-your-first-github-copilot-custom-agent-a-practical-loadrunner-example-4bo0>
> Published: 2026-09-24 04:51:35+00:00

A good Copilot conversation can solve a one-off problem. A custom agent turns that conversation into a repeatable engineering capability: a named persona with a defined job, a controlled tool surface, and domain knowledge that can be versioned with the repository.

This post is a practical guide to building one. It uses a LoadRunner Agent as the running example, but the design applies equally well to agents for test automation, incident response, documentation, code review, or platform operations.

The goal is not to create a longer system prompt. The goal is to create a small, inspectable workflow that your team can select from Copilot Chat and trust to produce the same kind of result every time.

A GitHub Copilot custom agent is a scoped, named persona with its own instructions, tools, and optional skills. It is defined by a Markdown file with YAML frontmatter and is discoverable from the Chat agent dropdown.

A custom agent can:

`LoadRunner Agent`.` EXTRACT -> GENERATE -> REVIEW`.
The important distinction is between ad-hoc prompting and an engineered agent. A prompt says what you want right now. An agent defines how a class of requests should be handled, which files may be touched, what checks must run, and what a complete result looks like.

The usual location is:

```
.github/agents/<name>.agent.md
```

The agent then appears as a selectable mode in Copilot Chat. Its instructions are not hidden in one engineer's chat history; they are reviewable, changeable, and shareable in source control.

GitHub Copilot customization has four closely related primitives. They solve different problems, so choosing the right one matters.

| Primitive | Purpose | Typical file | How it is activated | 
|---|---|---|---|
| Agent | A named persona with tools and a system prompt | `.agent.md` | Selected from the Chat agent dropdown | 
| Skill | Focused domain knowledge or a procedural playbook | `.SKILL.md` | Loaded on demand by an agent or user | 
| Instruction | Always-on rules scoped to matching files | `.instructions.md` | Applied automatically through `applyTo` | 
| Prompt | A reusable, parameterized slash command | `.prompt.md` | Invoked with `/prompt-name` | 

There are also repository-wide files that help discovery and consistency:

| File | Purpose | Location | 
|---|---|---|
| `copilot-instructions.md` | Rules every chat should see in the workspace | `.github/` | 
| `AGENTS.md` | Repository-level agent index or discovery guide | Repository root | 

A useful rule of thumb is simple:

Do not put every rule in the agent body. A large prompt becomes difficult to review, expensive to load, and easy to contradict. Keep the agent responsible for orchestration and move detailed procedures into skills.

The relationship between these files is easier to understand as a flow:

``` php
flowchart LR
    U[Engineer request] --> A[Custom agent]
    A --> I[System instructions]
    A --> S[Load matching skills]
    A --> T[Call allowed tools]
    I --> T
    S --> T
    T --> F[Files, commands, and search]
    F --> R[Reviewed output]
```

The agent is the coordinator. Skills provide specialized knowledge. Instructions provide rules that are always in force for a matching file pattern. Tools provide the ability to inspect, modify, or execute.

That separation creates useful boundaries:

`.agent.md` file
An agent file has two parts: YAML frontmatter at the top and a Markdown system prompt below it.

A minimal example looks like this:

```
---
name: LoadRunner Agent
description: Generates, reviews, and summarizes LoadRunner Web Vuser scripts from API project files.
tools:
  - execute/runInTerminal
  - read/readFile
  - edit/createFile
  - edit/editFiles
  - search/codebase
  - search/fileSearch
---

You are an expert LoadRunner script engineer.

Operate in three phases:
EXTRACT -> GENERATE -> REVIEW.

Do not skip a phase. Keep generated files inside the configured output directory.
```

The four important fields are:

| Field | Role | 
|---|---|
| `name` | The label shown in the Chat agent dropdown | 
| `description` | Helps users and Copilot understand when this agent is relevant | 
| `tools` | The allow-list of tool or toolset IDs available to the agent | 
| Body | The system prompt containing phases, rules, boundaries, and completion criteria | 

The description deserves more care than it usually gets. It should say what the agent produces, what inputs it understands, and when it should be selected. A vague description makes the agent harder to discover and easier to misuse.

The most disruptive failure in the walkthrough was also one of the easiest to miss: an agent can appear to load while silently losing the tools it needs because the IDs are wrong.

Use namespaced IDs in the form:

```
category/toolName
```

For example:

```
tools:
  - execute/runInTerminal
  - read/readFile
  - edit/createFile
  - edit/editFiles
  - search/codebase
  - search/fileSearch
```

The category communicates the capability boundary:

`execute/` for commands and terminal execution.`read/` for reading files and content.`edit/` for creating or modifying files.`search/` for codebase, file, or text search.
These forms are not interchangeable with informal names such as `runCommands`, `editFiles`, `create_file`, or `read_file`:

```
# Incorrect: raw tool names or toolset names
 tools:
   - runCommands
   - editFiles
   - create_file
   - read_file
# Correct: namespaced tool IDs
 tools:
   - execute/runInTerminal
   - read/readFile
   - edit/createFile
   - edit/editFiles
   - search/codebase
```

An unknown ID may be dropped without an obvious YAML error. The resulting agent still appears in Chat, but it cannot write files, run the extractor, or search the repository as intended.

After changing the YAML, open the Chat tools picker and verify the exact tool IDs available to the agent. Then reload the agent. Configuration changes do not necessarily hot-reload into an already active chat session.

Tool IDs are a version-sensitive integration detail, not a universal API contract. Treat the names in this post as examples for the environment in which the agent was authored. The Chat tools picker is the source of truth for the exact IDs available in your VS Code installation.

A skill is a focused, self-contained playbook for one capability. Skills keep the main agent prompt short while allowing the workflow to carry detailed rules.

A typical location is:

```
.github/agents/skills/<name>.SKILL.md
```

For the LoadRunner Agent, the skills can map directly to the workflow:

```
.github/agents/skills/
    lr-extract.SKILL.md
    lr-generate.SKILL.md
    lr-review.SKILL.md
    lr-parameterize.SKILL.md
    lr-boilerplate.SKILL.md
```

Each file should have one job:

| Skill | Responsibility | 
|---|---|
| `lr-extract` | Detect SoapUI or Postman input and produce normalized request files | 
| `lr-generate` | Convert one normalized request into a LoadRunner C script | 
| `lr-review` | Run the quality checklist and summarize findings | 
| `lr-parameterize` | Build data files from extracted values | 
| `lr-boilerplate` | Generate functional overview documentation | 

A skill should tell the agent what to read, what to produce, which rules to apply, and how to recognize success. It should not assume that the agent remembers a procedure from an earlier conversation.

The agent can chain several skills when the task crosses phases. That is more maintainable than putting extraction rules, C coding conventions, and review criteria into one giant system prompt.

The example agent converts SoapUI or Postman API project files into LoadRunner Web Vuser scripts. Its workflow has three explicit phases:

``` php
flowchart TD
    A[Input files in input/] --> B{Detect format}
    B -->|SoapUI XML| C[Run SoapUI extractor]
    B -->|Postman JSON| D[Run Postman extractor]
    C --> E[One normalized .txt per request]
    D --> E
    E --> F[Read one request]
    F --> G[Generate one .c script]
    G --> H{More requests?}
    H -->|Yes| F
    H -->|No| I[Run review checklist]
    I --> J[Pass/fail table and recommendations]
```

The agent auto-detects input files in the configured input directory, selects the appropriate extractor, and produces one normalized text payload per API request.

This phase should answer:

The extraction result is the contract for the generation phase. If the contract is ambiguous, generation should stop and report the problem instead of guessing.

The agent reads each normalized request one at a time and writes one C script per request. The one-at-a-time rule is deliberate. It avoids loading all payloads into context, reduces cross-request confusion, and makes it clear which input produced which output.

The generation skill can encode rules such as:

`web_reg_find`.
The agent should write the output immediately after processing each input:

```
read request-001.txt
write request-001.c
read request-002.txt
write request-002.c
read request-003.txt
write request-003.c
```

That loop is safer than reading every input file first and attempting to generate all scripts at the end.

The review phase runs a fixed checklist against the generated scripts. It should return evidence, not just a statement that the files look correct.

A useful result contains:

The review phase is where an agent becomes more than a file generator. It gives the team a consistent quality gate after generation.

The system prompt should be explicit about phases and boundaries. For example:

```
You are an expert LoadRunner script engineer.

Your job is to convert SoapUI XML or Postman JSON project files into reviewed
LoadRunner Web Vuser scripts.

Always operate in this order:

1. EXTRACT
   - Detect supported input files under input/soap/ or input/postman/.
   - Run the matching extractor.
   - Confirm the normalized request files that were created.

2. GENERATE
   - Read exactly one normalized request file at a time.
   - Create exactly one .c file for that request.
   - Apply every rule in lr-generate.SKILL.md.
   - Continue until every normalized request has an output file.

3. REVIEW
   - Run every item in lr-review.SKILL.md.
   - Produce a pass/fail table.
   - Report the top three issues and top five recommendations.

Do not invent missing request data. Do not skip extraction or review.
Do not batch-read large collections of input files.
Do not create helper scripts to replace this workflow.
Keep all outputs inside the repository's documented directories.
```

The details belong in skills, but the agent body must still define the non-negotiable orchestration rules. In particular, say what the agent must not do. Otherwise a general-purpose agent may decide that generating a helper script is a convenient shortcut, even when the agent itself is supposed to perform the work.

The smallest useful implementation can be three files. The agent coordinates the workflow, the skill contains the generation rules, and the workspace instructions lock down paths and naming.

`.github/agents/loadrunner.agent.md`:

```
---
name: LoadRunner Agent
description: Converts API project files into reviewed LoadRunner Web Vuser scripts.
tools:
    - execute/runInTerminal
    - read/readFile
    - edit/createFile
    - edit/editFiles
    - search/fileSearch
---

Convert supported files under input/ into LoadRunner scripts under output/scripts/.

Always run these phases in order:
1. EXTRACT: identify the input format and create normalized request files.
2. GENERATE: read one normalized request and write one .c file at a time.
3. REVIEW: run the checklist in lr-generate.SKILL.md and report failures.

Do not invent missing request data, batch-read the entire input directory, or create
helper scripts. Keep all generated files under output/.
```

`.github/agents/skills/lr-generate.SKILL.md`:

```
# LoadRunner generation rules

For each normalized request:

- Preserve the HTTP method, URL, headers, and request body.
- Use a stable transaction name derived from the request name.
- Register an expected response with web_reg_find when a reliable check exists.
- Add required Dynatrace headers.
- Add realistic think time only at business-flow boundaries.
- Report missing correlation candidates instead of guessing replacements.
- Write exactly one reviewed candidate script for the input request.
```

`.github/copilot-instructions.md`:

```
- Read source inputs from input/.
- Write generated scripts only to output/scripts/.
- Write review summaries only to output/reports/.
- Use one output file per normalized request.
- Never store credentials, tokens, or production data in generated files.
```

This starter set is intentionally small. Add extraction, parameterization, and review skills as the workflow gains real requirements, rather than making the first agent responsible for every possible LoadRunner task.

Large input collections create two predictable problems:

A strict loop provides a small checkpoint after every item:

```
for each normalized request:
    read one request
    generate one output
    verify the output exists
    continue
```

This is a workflow rule, not a performance optimization. It makes failures local, recoverable, and visible. It also lets a user stop after a particular request without discarding all completed work.

An agent's tool list is an allow-list, but it is not a complete security boundary. A tool that can run a terminal command or write a file can still have meaningful side effects. Design the agent with the same caution you would apply to a CI job or an automation service account.

The agent should also report what it did: files read, commands run, files created, warnings, and assumptions. That audit trail makes a generated result easier to review and easier to investigate when something goes wrong.

**Symptom:** The agent loads but cannot create files or run commands.

**Cause:** The `tools` list contains raw tool names or unsupported toolset names.

**Fix:** Use namespaced IDs such as `edit/createFile` and `execute/runInTerminal`. Verify them in the Chat tools picker after every YAML change.

**Symptom:** Instead of processing the inputs, the agent creates a Python or shell utility intended to do the work later.

**Cause:** The prompt did not clearly state that the agent is the program and that helper-script generation is out of scope.

**Fix:** Add an explicit prohibition to both the agent instructions and the relevant skill. State the permitted tools and the required output directly.

**Symptom:** The agent summarizes the input collection, loses details, or repeatedly rereads files.

**Cause:** Large payloads were loaded in one operation.

**Fix:** Mandate the one-file-in, one-file-out loop and write each result before moving to the next input.

**Symptom:** The YAML is correct, but the agent still cannot call a required tool.

**Cause:** The tool was unchecked in the Chat tool picker, or the chat session was created before the configuration changed.

**Fix:** Enable the tool, reload the agent, and start a fresh session when necessary.

**Symptom:** Different runs create different folder layouts or naming conventions.

**Cause:** The required paths were implied instead of declared.

**Fix:** Lock folder and naming conventions in `.github/copilot-instructions.md` and repeat the output contract in the agent or skill that writes the files.

**Symptom:** The old description or tool set remains active after editing the agent file.

**Cause:** The active agent instance has not reloaded its configuration.

**Fix:** Reload the agent and verify the current tool list before debugging the workflow itself.

A small project can begin with this structure:

```
.github/
    agents/
        loadrunner.agent.md
        skills/
            lr-extract.SKILL.md
            lr-generate.SKILL.md
            lr-review.SKILL.md
            lr-parameterize.SKILL.md
            lr-boilerplate.SKILL.md
    instructions/
        loadrunner.instructions.md
    prompts/
        review-loadrunner.prompt.md
    copilot-instructions.md
AGENTS.md
input/
    soap/
    postman/
output/
    scripts/
    reports/
```

The names are conventions, not requirements. What matters is that the locations and naming rules are stable enough for the agent, the team, and code review to agree on where things belong.

A useful division of responsibility is:

`.SKILL.md`: detailed rules for extraction, generation, parameterization, and review.`.instructions.md`: file-pattern-specific coding conventions.` copilot-instructions.md`: workspace-wide rules and path conventions.`.prompt.md`: shortcuts for recurring user requests.` AGENTS.md`: a human-readable index of available agents and their intended jobs.
Before sharing a custom agent, verify the following:

`.github/agents/` and is committed with the repository.
Run a deliberately small test after creating or changing the agent:

This test catches the most expensive configuration failures early: missing tools, wrong paths, skipped phases, accidental batching, and silent guessing.

Custom agents are small software systems. Their Markdown files may look simple, but the same engineering concerns still apply: interface contracts, permissions, state, failure handling, observability, and tests.

The agent is the orchestration layer. Skills are the domain modules. Instructions are the policy layer. Tools are the execution boundary. A reliable result comes from designing those layers together, then checking that the selected tools and repository paths match the design.

For the LoadRunner example, the winning pattern is straightforward:

``` php
EXTRACT -> GENERATE -> REVIEW
```

Make each phase explicit, keep each skill focused, process large inputs incrementally, and make tool permissions visible. The result is not merely a clever prompt. It is a repeatable engineering workflow that a team can inspect, improve, and run from GitHub Copilot.

| Need | Put it in | Example | 
|---|---|---|
| Named specialist with tools | `.agent.md` | `loadrunner.agent.md` | 
| Detailed domain procedure | `.SKILL.md` | `lr-review.SKILL.md` | 
| Rule for matching files | `.instructions.md` | `loadrunner.instructions.md` | 
| Reusable slash command | `.prompt.md` | `review-loadrunner.prompt.md` | 
| Workspace-wide rule | `copilot-instructions.md` | Output paths and naming | 
| Agent discovery index | `AGENTS.md` | Available agents and responsibilities | 

Common tool ID families:

| Tool family | Example | 
|---|---|
| Execute | `execute/runInTerminal` | 
| Read | `read/readFile` | 
| Create | `edit/createFile` | 
| Modify | `edit/editFiles` | 
| Semantic search | `search/codebase` | 
| File search | `search/fileSearch` | 
| Text search | `search/textSearch` | 

Start with one narrow agent, one clear workflow, and one review checklist. Add skills as the workflow grows; do not make the first agent responsible for everything.

Documentation and available tool IDs change over time. Check the current VS Code documentation and the Chat tools picker before copying a configuration into a production repository.
