Some roles repeat from task to task: check changes to a public API, walk a diff for regressions, inspect code without the right to touch it. Built-in subagent profiles are general, while such a role is narrow and permanent, and describing it anew in the prompt each time is wasteful and unreliable.
The naive move is to keep the role's wording in your head or a note and paste it into the general prompt whenever such a pass is needed. As long as you do it from memory, the role drifts: today you asked for read-only, tomorrow you forgot that, and a general agent with the full tool set began editing where you expected only an inspection.
It breaks on scope drift. A general agent given a reviewer's task is still armed with write and exec; let the instruction weaken a little, and it steps out of the role because it technically can. A role that lives only in the words of the prompt has no boundary: it rests on the discipline of the wording, not on a mechanism, and so it is unreliable exactly where it matters.
The professional mechanism is to move the role into a separate subagent profile file. A project profile lives in .devin/agents/<name>.md or .devin/agents/<name>/AGENT.md; the cross-tool path .agents/agents/ is also supported. The frontmatter holds the name, a description and allowed-tools, the body the role's actual instructions. Such a profile sits in the repository, is read at review, and travels with the project.
The key detail here is allowed-tools. This is not a wish in the text but a boundary of execution: a subagent with allowed-tools of read, grep and glob physically cannot write to files, however its task is worded. The role stops resting on prompt discipline and starts resting on the list of permitted tools - that is, on the same principle as all of Devin Local's permission model.
What this looks like in practice is shown by a short API-contract reviewer profile. The frontmatter sets a name, a human-readable description and three read-only tools; the body states the narrow task - inspect the diff without changing files, check status codes, the request and response schema, backward compatibility and negative cases, and return findings with file:line and severity. The profile will let it do nothing beyond that.
The format of such profiles is experimental at the date of this snapshot, and that dictates the style. Keep definitions small and one clear role per file; do not build much logic into them that may not survive a format change, and check the changelog before relying on a specific field. A small profile is easier both to verify and to fix when the format shifts.
A separate line about nesting. The max-nesting field lets a subagent spawn child agents, and on paper that is tempting - a role that hands out subtasks itself. In practice nesting quickly raises both the spend and the difficulty of observation: a tree of agents is harder to follow, and the bill grows with each level. Do not enable nesting without an explicit need; by default a narrow role should be flat. If subtasks really are needed, it is more honest to move them into separate named profiles than to hide them in the depth of one.
A custom subagent is justified when the role really is narrow and repeats. A one-off pass is not worth a file - it is easier to describe in the prompt itself. But a reviewer needed on every diff, or a scout that always only reads, repays the formalization: a role described once stops depending on whether you remembered all its limits today. What justifies the formalization is the role's repeatability, not its complexity.
You must verify a profile by behavior, not by text. Run it on a known diff and confirm it stayed within allowed-tools - did not touch files, returned findings in the promised form with file:line and severity. Then open Customizations and see what actually loaded: a profile sitting in the repository and a profile applied in the session are not the same thing until you have seen the second with your own eyes. It helps to run the profile on a diff that deliberately contains a violation, too - and confirm it names the violation rather than staying silent.
The typical failures are recognizable. The first is giving the reviewer write tools "just in case" and getting an agent that, instead of a review, starts fixing, erasing the role's boundary. The second is enabling nesting without need and being surprised by the spend and by the tree of subagents you can no longer track. The third is relying on an experimental field as if it were stable and finding after an update that the profile is read differently. The sign of all three is the same: the role was described in words but not grounded in allowed-tools and not checked against what actually loaded.
---
name: api-contract-reviewer
description: Reviews changes to the public HTTP API
allowed-tools:
- read
- grep
- glob
---
Inspect the diff without modifying files.
Check status codes, request/response schema,
backward compatibility and negative cases.
Return findings with file:line and severity.