Skip to content

Custom Agents in GitHub Copilot CLI

Lesson 5 of 9Advanced13 min readGitHub Copilot & AI Engineering · Copilot CLIVerified: GitHub Copilot custom agents configuration reference, September 2026

A custom agent is a Copilot with a job.

Instead of a general assistant that can do anything, it is a profile with specific expertise, specific instructions, and — the part that matters most — a specific set of tools it is allowed to use.

That last property is what makes custom agents a security mechanism rather than a convenience. An instruction saying “do not edit files” is a request. An agent whose tool list contains only read and search cannot edit files.

An agent is a Markdown file with YAML frontmatter.

.github/agents/terraform-reviewer.md
---
name: terraform-reviewer
description: Reviews Terraform for security and correctness. Read-only.
tools: ["read", "search"]
---
You review Terraform configuration. You do not modify anything.
Check, in order:
1. Resources without tags required by our tagging policy.
2. Security groups or firewall rules open to 0.0.0.0/0.
3. Storage without encryption at rest enabled.
4. IAM policies with wildcard actions or resources.
5. Resources that would be replaced rather than updated by a change,
and what that replacement destroys.
6. State-affecting changes: moved blocks, removed resources, provider version
changes.
For each finding: the file, the resource, why it matters, and how confident you
are. If a finding depends on a file you have not read, say so.

File naming. The configuration file’s name minus .md or .agent.md is used for deduplication between levels — both extensions are recognised.

Locations, in scope order:

LevelLocation
Repository.github/agents/NAME.md
Organisation/agents/NAME.md in the organisation’s .github or .github-private repository
Enterprise/agents/NAME.md in the enterprise .github-private repository
PersonalYour user profile

The organisation level is the interesting one: a well-built reviewer agent defined once is available across every repository, without copying.

FieldPurpose
descriptionRequired. What the agent is for and when to use it
nameDisplay identifier
toolsThe tools the agent may use
modelWhich model to use
disable-model-invocationWhether the model can invoke this agent automatically
user-invocableWhether it can be selected manually
mcp-serversMCP server definitions (cloud agent)
metadataCustom annotations (cloud agent)
targetRestrict to vscode or github-copilot; omit for both

description is required and is load-bearing. It is how the agent gets selected — both by you browsing /agent, and by the model when deciding whether to delegate to it. A vague description produces an agent that is never chosen or is chosen for the wrong things.

tools is the security control. More on this below.

The most important idea on this page.

Everything else in Copilot’s configuration surface influences behaviour. Instructions, skills and prompt files all shape a probabilistic process. A tool list constrains it: an agent without an edit tool cannot edit, regardless of what it decides is a good idea.

That difference matters most for exactly the agents you would most want to trust — reviewers, auditors, anything looking at code it should not change.

{/* Can read and search. Cannot edit, cannot run commands. */}
tools: ["read", "search"]
{/* Can also run commands — a much larger grant */}
tools: ["read", "search", "shell"]
{/* Can change things */}
tools: ["read", "search", "edit", "shell"]

The discipline: start from nothing and add what the job needs. An agent that reviews does not need edit. An agent that writes documentation does not need shell. Each tool added is a capability the agent has in every situation, including ones you did not anticipate.

Four agents that earn their place.

.github/agents/docs-agent.md
---
name: docs-agent
description: Writes and updates documentation derived from actual repository state.
tools: ["read", "search", "edit"]
---
You write documentation for this repository.
Rules:
- Derive everything from files you have actually read. For each factual claim,
be able to name the file it came from.
- Where you are inferring intent rather than reading it, say so in your response
(not in the document).
- Do not write reasons for architectural decisions unless a commit message,
comment or document states them.
- Do not generate setup instructions you have not verified against the manifest,
the Dockerfile and the CI workflow.
- Update existing documents rather than regenerating them, so previous
corrections survive.
- Keep human documentation separate from `.github/copilot-instructions.md`.

The last three rules address the specific failures in AI repository documentation: invented rationale, unverified setup steps, and regeneration discarding corrections.

.github/agents/test-writer.md
---
name: test-writer
description: Writes tests following this repository's conventions, and verifies
they fail when the code is broken.
tools: ["read", "search", "edit", "shell"]
---
You write tests for this repository.
- pytest, with fixtures from `tests/conftest.py`. No unittest classes.
- Name tests `test_<function>_<condition>`.
- Cover the happy path, empty and null inputs, boundaries, and each error branch.
- Use the `db_session` fixture for anything touching the database.
After writing a test, verify it:
1. Run it. It should pass.
2. Break the code it covers — invert a condition or remove a guard.
3. Run again. **The test must fail.** If it does not, the test asserts nothing;
fix it.
4. Restore the code and confirm the test passes.
Report any behaviour you could not test and why.

This one needs shell — running the tests is the entire point, and the verify-by-breaking step is what distinguishes a useful test from one that passes vacuously.

.github/agents/security-reviewer.md
---
name: security-reviewer
description: Reviews changes for the security issues that matter in this codebase.
Read-only.
tools: ["read", "search"]
---
Review the changes you are given for security issues, in this order:
1. Input reaching a query, command, file path or template without going through
the helpers in `security/sanitise.py`.
2. New endpoints without an authorisation decorator — there is no default-deny
middleware.
3. Secrets or credentials in code, configuration or test fixtures.
4. Logging of request bodies or personal data.
5. New dependencies. Name them and say what they are for.
6. Changes to authentication, session handling or permission checks.
For each finding: file, line, why it matters here, and your confidence.
If you find nothing in a category, say so explicitly rather than omitting it.
Do not propose edits. Report only.

Read-only is deliberate and important: a security reviewer that can edit is a security reviewer that can “fix” something in a way nobody reviewed.

.github/agents/migration-reviewer.md
---
name: migration-reviewer
description: Checks database migrations against our operational rules. Read-only.
tools: ["read", "search"]
---
Check each migration:
- Is it reversible, with a correct `down` — not merely present?
- Does it drop or rename a column that code still references? Search for usages.
- Does it add a NOT NULL column without a default or a backfill?
- Does it create an index non-concurrently on a table over a million rows?
- Would it hold a lock long enough to matter under production load?
- Does it combine a schema change with a data migration? Those should be
separate releases.
State each check and its result. Do not skip checks that pass.

An agent can spawn subagents to handle delegated work in an isolated context — a separate agent running a subtask, returning a result, without its reading entering the main conversation.

Two properties matter.

Context isolation. A subagent asked to search forty files returns an answer; the forty files stay in its context, not yours. This is the mechanism that keeps a long task coherent.

Specialisation. A main agent doing a broad task can delegate the security review portion to the security agent, which has the right instructions and the right tool restrictions for that part.

/fleet enables parallel subagent execution for work that decomposes.

The caution: subagent output is a summary. What comes back is the subagent’s account of what it found, subject to the same “an account is not evidence” rule as everything else. For anything consequential, check what it actually did.

The three mechanisms compose, and knowing how avoids duplicating content across them.

Instructions apply regardless of which agent is running. Repository conventions, the stack, the frozen directories — an agent inherits all of it and should not restate it.

The agent file carries what is specific to this role: the criteria, the ordering, the tool restrictions, the output format.

Skills carry long procedures the agent loads when relevant. A security agent whose file lists six check categories can reference a skill containing the full two-page procedure for one of them, loaded only when that category is in play.

Concretely, for a security reviewer:

.github/copilot-instructions.md "All input goes through security/sanitise.py"
.github/agents/security-reviewer.md "Check these six things, in this order, read-only"
.github/skills/threat-model/SKILL.md "The full threat modelling procedure, with examples"

Three files, no duplication, each loaded at a different time. The instruction costs context on every request; the agent file costs context when that agent runs; the skill costs context only when threat modelling is relevant.

That layering is the payoff for understanding the mechanisms rather than picking one and using it for everything.

An agent file is configuration that produces variable output, which makes “does it work” worth answering deliberately.

Run it on something you already understand. A file you know has three problems. Does it find them? Does it invent a fourth?

Run it on something clean. A reviewer given nothing to find should say so rather than manufacturing findings. This failure is common — a prompt asking for findings tends to produce findings — and it is the one that makes people stop reading the output.

Run it twice on the same input. High variation means the criteria are too loose and the agent is filling gaps with its own choices.

Check the tool restriction actually holds. Ask a read-only agent to make a change. It should be unable rather than unwilling, and the difference is observable.

Have somebody else run it. They will discover every assumption the file makes about what to attach and what the output should look like.

Twenty minutes, and it is the difference between an agent that works for its author and one that works.

Terminal window
{/* Browse and select from available agents */}
/agent
Terminal window
{/* Show everything loaded, including agents */}
/env

/add-dir loads a directory’s .github/agents alongside its skills, which is worth remembering when working across repositories — you are adopting that directory’s agents as well as granting file access.

disable-model-invocation and user-invocable control whether an agent can be chosen automatically by the model or only selected by you. For an agent with broad tools, requiring manual selection is a reasonable precaution.

Three frontmatter fields that shape how an agent is used, and are easy to overlook.

model picks the model for this agent’s work. Useful where the role has a clear latency or capability profile: a quick mechanical checker benefits from a fast model; a security reviewer reasoning across several files benefits from a more capable one. Availability depends on plan and policy, and specific model names age quickly — the decision worth encoding is the trade-off, not a name you will have to revisit.

disable-model-invocation stops the model choosing this agent automatically. For an agent with edit and shell, requiring deliberate selection is a reasonable precaution — automatic delegation to a broadly-capable agent is capability granted without a decision.

user-invocable controls whether you can select it manually. Setting this false makes an agent usable only as a delegation target, which suits a narrow subagent that has no standalone use.

The combination worth considering for anything powerful: user-invocable: true and disable-model-invocation: true — you can choose it, and nothing chooses it for you. That is a small configuration decision with a real effect on when a capable agent runs.

One job. An agent that reviews security, writes documentation and runs tests is a general assistant with extra steps. The value comes from narrowness — both because the instructions can be specific and because the tool list can be minimal.

A description that says when to use it. This is how it gets selected, by you and by the model.

The minimum tool set. Start with read and add deliberately.

Explicit criteria, in order. A numbered list of what to check produces consistent output; “review this thoroughly” does not.

Ask for confidence and for gaps. “Say how confident you are” and “if a finding depends on code you have not read, say so” are the two lines that make output triageable.

Say what not to do. “Do not propose edits.” “Do not skip checks that pass.” Naming the undesirable shortcut is more effective than describing the desirable behaviour.

The scaling mechanism, and the one worth setting up once.

An agent defined in /agents/NAME.md in an organisation’s .github or .github-private repository is available across every repository in that organisation. A well-built security reviewer, migration checker or documentation agent is then a shared asset rather than something each team reinvents.

Three consequences worth planning for.

They need an owner. An organisation-level agent shapes results everywhere. That is a shared artefact with the same review requirements as an organisation ruleset — a pull request, a reviewer, and somebody accountable for it.

They must be genuinely general. An agent encoding one team’s conventions is wrong everywhere else, and no repository-level file cleanly overrides it. The test is the same as for organisation instructions: is this true across the whole organisation?

Deduplication is by name. The configuration file’s name minus the extension deduplicates between levels, so a repository-level security-reviewer.md takes precedence over an organisation one of the same name. That is the mechanism for a team needing a variant — define it locally with the same name rather than a different one.

The pattern that works: general agents at the organisation, specialised ones in the repository, and a deliberate decision about which is which rather than defaulting to local because it is easier.

Custom agents are configuration to maintain, and not every specialised need warrants one.

A one-off task is a prompt. Writing an agent file for something you will do once is overhead.

A repeated prompt with no tool implications is a prompt file or a skill. If the only thing you want is consistent instructions for a task, an agent adds a selection step for no benefit.

A rule that should always apply is an instruction. An agent that exists to enforce a convention on every request is the wrong shape — instructions already apply to every agent.

Something that must never happen is a hook or a ruleset. An agent’s tool list constrains that agent; it does nothing about what a different agent, or a person, does.

The cases where an agent genuinely earns its place:

Tool restriction matters. A reviewer that must not edit. This is the strongest reason.

A distinct role with distinct criteria. Security review and documentation writing want different instructions, different output formats and different tools.

Delegation to a specialist. A main agent handing the security portion of a task to something configured for it.

Consistency across a team. Everybody’s security review checks the same six things, because the criteria live in a reviewed file rather than in whoever is prompting.

If none of those apply, a prompt or a skill is less to maintain.

Relying on instructions instead of tools. “Do not edit” with edit available is a request.

A vague description. The agent is never selected, or selected for the wrong things.

One agent doing several jobs. The narrowness is the value.

Giving shell because it might be useful. Every tool is a capability in every situation.

Not testing it. Run it on something you already understand and check whether the output matches what you would have found.

Duplicating repository instructions into every agent. Instructions already apply; the agent file is for what is specific to this role.

Forgetting /add-dir loads agents. Scope decision, not just file access.

Never reviewing them. An agent file is configuration that shapes results and grants capability; it decays like any other, and a stale criterion steers review the wrong way.

Agent definitions are configuration that grants capability, which makes the files a security surface.

They are code and belong in review. A pull request adding an agent with shell and edit is granting a capability. A CODEOWNERS entry on .github/ covers this alongside workflows.

A pull request can add one. In a repository accepting outside contributions, a contributor can propose an agent file. Whether it takes effect depends on the surface — but the general principle from secure AI code review applies: configuration in the branch under review is part of the diff, and reviewing the diff means reviewing the configuration too.

mcp-servers in an agent definition grants tool access. An agent file that declares MCP servers is declaring what external systems that agent can reach. That is a bigger grant than a tool name in a list, and it deserves proportionate scrutiny — see MCP + GitHub.

/add-dir adopts agents. Pointing the CLI at another directory loads its agents. If that directory is a repository you do not control, you have adopted its agent definitions.

None of this is exotic. It is the observation that a directory which now grants tools and shapes behaviour has become part of what you review, and treating it as a preferences folder is how it stops being reviewed.

A custom agent is a job description plus a set of keys. The description shapes what it does; the keys determine what it can do. When something must not happen, take away the key rather than adding a sentence to the description.

  • Custom agents are Markdown with YAML frontmatter, as NAME.md or NAME.agent.md
  • They live at repository, organisation, enterprise and personal levels
  • description is required and determines how the agent gets selected
  • tools is the security control; instructions are a request
  • Start from read and add tools deliberately
  • Subagents isolate context and can carry specialised restrictions
  • Subagent output is a summary and is subject to the usual scepticism
  • /add-dir loads that directory’s agents as well as its skills
  • Explicit ordered criteria and a confidence request make output triageable

Use a disposable repository.

  1. Write a review agent with tools: ["read", "search"]. Ask it to fix something it finds. Predict: what happens?

  2. Add edit to its tools and ask again. Compare.

  3. Write an agent with a vague description — “helps with code”. Run /agent. Predict: would you know when to use it?

  4. Write a testing agent that verifies tests by breaking the code. Run it. Predict: does it find a test that passes vacuously?

  5. Add an explicit “state your confidence” instruction to a reviewer and rerun. Compare how quickly you can triage.

  6. Use /add-dir on a directory containing its own agents. Run /env. Predict: what is now loaded?

  7. Delete the repository.

AI-assisted engineering learning pathEleven lessons on getting value from Copilot and agents without giving up review discipline.