Skip to main content

Delegate definitions

A delegate is a second agent with its own context: it does a sub-task and hands back one report, so the reading it did stays with it. Three kinds come with bravebot (reader, checker and worker), and a definition is one more, written down rather than compiled in.

Put one in ~/.bravebot/agents/<name>.md and it is available in every project; put it in <workspace>/.bravebot/agents/<name>.md and it belongs to that project.

---
name: rule-reviewer
description: Checks a diff against the rule in docs/development/reviewing-for-the-rule.md. Use before asking anyone to review a label change.
kind: reader
model: haiku
tools: read_file, list_files
---

Read the diff and the four shapes a violation takes. Report the shape and the file, or say none.

The planner can then ask for a rule-reviewer the way it asks for a reader, and what that delegate is told about itself is the body of your file. Without one, the standing part of the instruction, the part that is the same every run, has nowhere to live: it has to be written into the task afresh every time.

You can also run one yourself, without the planner choosing it: /agent rule-reviewer check this branch. That is a turn of your own under the definition's body, model and narrowing rather than a delegate, so it can still ask you questions and what it reads stays in the conversation.

To address every turn of a session to it, start the session with bravebot --agent rule-reviewer. That includes /loop ticks and /goal rounds, and /agent other <task> still addresses another definition for one turn. bravebot --agent rule-reviewer -p "check this branch" does the same for one run. A one-shot run does not ask whether to trust the checkout, so it reads only the definitions in ~/.bravebot/agents.

The keys​

KeyRequiredMeaning
nameyeswhat the planner names to select it
descriptionyeswhat the planner decides from, so say when to use it rather than what it does
kindyesreader, checker or worker
modelnothe model this delegate runs on (haiku, sonnet, opus, or an explicit model identifier); absent or inherit means the spawning turn's
toolsnofewer tools than the kind's; absent means the kind's own
skillsnothe skills this delegate is offered, out of the ones the turn found; absent means all of them, and an empty line none
mcpServersnothe MCP servers a worker calls, by alias, out of the ones the turn may; absent means all of them unless tools is written, and an empty line none
roundsnohow many rounds of tools this delegate may take before it has to answer, up to its kind's ceiling; absent or empty means the kind's own
memorynoproject or local keeps a memory, a file its runs read and write themselves; absent or empty means none
isolationnocheckout or worktree gives each of its delegates a checkout of its own to work in; absent or empty means none
bodynothe standing instruction

Keys other than these are ignored rather than refused, so a definition written for another agent loads here too. That includes another agent's bound, maxTurns or steps: a definition ported with one runs at its kind's own limit until you add a rounds line.

model chooses what the delegate runs on. A tier alias (haiku, sonnet, opus) or an explicit model identifier resolves through configuration the way any named model does. This lets a high-volume delegate like a build checker run on a cheap model rather than spending the turn's model on reading thousands of lines of logs, while a refactoring worker can select a stronger model. Where model is omitted, the delegate inherits the model of the turn that spawned it. Where the named model needs a sign-in you have not made, the delegate does not run and says so, rather than falling back to the turn's model, so an intended cost control cannot be silently bypassed. A delegate answered by a different model than the one named says so, so a misspelt name is not silent.

kind picks what the delegate may do, and your file never describes it. A reader reads, lists and searches; a checker also runs programs and asks a language server; a worker also writes files and calls the tools of the MCP servers the turn that spawned it may. Each still asks you before every write, every command it runs and every call to a server's tool: delegating saves the agent context, never an approval.

tools can only take things away. It names a subset of what the kind already reaches, and a tool the kind does not reach is one the delegate is started without. There is no spelling of it that adds a capability, including *, which is read as a tool name matching nothing rather than as "all of them". That is the deliberate difference from tools where the same key is the permission list: a file in a repository you cloned cannot hand an agent a shell it was never granted.

mcpServers chooses which servers a worker calls. Name them by the alias you gave each in ~/.bravebot/mcp.json, on one line or as a list. The delegate calls those, where the turn that spawned it may, and no others. A server's tools are not bravebot's, so no tools line names one: a worker whose definition has a tools line and no mcpServers line calls no server, and one with neither line calls every server the turn may. A reader or a checker calls none whatever it names. A name no server goes by picks nothing, and the turn says so:

~/.bravebot/agents/forecaster.md names an MCP server this session did not reach, so its delegate runs without it: wether

Another agent's definition may describe a server inline under the same key. That starts nothing, since servers are declared in ~/.bravebot/mcp.json alone: the definition calls no server, and the turn says so without repeating the entry, which may hold a secret.

That includes spawn_agent, the tool a delegate starts delegates of its own with. A definition that names its tools and leaves it out, like rule-reviewer above, does its work itself and hands none of it on. Name spawn_agent in the list if it should be able to.

Name bravebot's own tools here. A definition ported from another agent usually names that agent's (Read, Grep, Bash(...)), and none of those is a tool here, so the delegate starts with no tools at all. It says which names it dropped, so the fix is to rename them.

skills chooses which of your skills the delegate is told about. A delegate given one job needs the skill for that job, not the whole list the turn found. Name them the way tools names tools, on one line or as a list. The delegate is listed those and can load no others. A name only picks from skills the turn already found, so it cannot load a skill from a directory you did not vouch for. A name no skill goes by picks nothing, and the turn says so:

~/.bravebot/agents/rule-reviewer.md names a skill this session did not find, so its delegate is offered without it: rule-reveiw

rounds sets how long the delegate may work. Nobody is watching a delegate, so each kind stops one after a set number of rounds and makes it answer with what it has: 60 for a reader, 80 for a checker and 120 for a worker. A definition written for a long job, a staged refactor say, can ask for more, up to its kind's ceiling: 120 for a reader, 160 for a checker and 200 for a worker, which is the bound on a one-shot run nobody is watching. Asking for more than that gives the delegate the ceiling, and the turn says so:

~/.bravebot/agents/migrator.md asks for 500 rounds, more than the 200 a worker may make, so its delegate is given 200

The value is a whole number above zero. An empty line is the same as none. Anything else, 0 or lots or 1.5, means the file does not load, and the turn names it. The planner cannot set a bound when it starts a delegate: only the definition can.

rounds bounds the definition only when the planner starts it as a delegate. A turn you run yourself with /agent is yours, and carries no bound, as any turn you are watching does.

A name may not open with -, may not contain a colon, which stays reserved for naming things inside a namespace, and may not be reader, checker or worker: those belong to the kinds, so that reader means the same thing in every project.

Memory​

A definition with memory: project or memory: local keeps notes from one run to the next, in one file named after it: .bravebot/memory/<name>.md in the directory the session is working in. Each run under it, a delegate the planner started or a turn you ran with /agent, is told where that file is and reads it itself. Nothing puts the notes in its prompt.

---
name: release-notes
description: Drafts the release notes for this branch. Use when asked what changed since the last tag.
kind: worker
memory: project
---

Keep the conventions the maintainers asked for in your memory, and follow them.

The run keeps the file up to date with the tools its kind already has, on the same terms as any other file it writes. memory adds no tool, so a reader or a checker can read a memory something else wrote and cannot change it.

project and local are the same file here: whether it is committed is up to you and your ignore rules, and bravebot writes none. Any other value, user included, loads the definition keeping no memory, and the turn says so:

~/.bravebot/agents/release-notes.md keeps no memory: its memory line says user, and only project and local keep one

A definition keeping a memory needs a name of lowercase letters and digits in runs joined by single hyphens, at most 64 characters, since the file is named after it. A session in your home directory keeps none, because the file would be inside ~/.bravebot.

A memory is read on the trust map's terms. In a directory you did not vouch for, the run is told its memory is withheld, and reading it is a quarantined read like any other. Where a write leaves the file untrusted, because it passed on something from a page or a file nobody vouched for, the path is recorded in ~/.bravebot/untrusted, and later sessions withhold the memory too. Saying yes when a read of it is quarantined, naming it with @, dropping or attaching it, or a later write that leaves it trusted, takes it out of that record. A write that cannot be recorded there is refused.

A checkout of its own​

A checker or worker definition with isolation: checkout has each of its delegates work in a checkout bravebot makes for it under ~/.bravebot/checkouts, holding the last commit of the repository the session is in. Its writes land there and not in your working tree, and changes you have not committed are not in it. worktree, the value Claude Code reads, asks for the same thing. The planner cannot start the delegate without one.

---
name: migrator
description: Moves one module to the new API. Use for a staged refactor.
kind: worker
isolation: checkout
---

Any other value loads the definition working in your working tree, and the turn says so:

.bravebot/agents/migrator.md is loaded without a checkout: its isolation line says none, and only checkout and worktree ask for one

A reader is never given one, since it writes nothing, and the turn says that too. Where a checkout cannot be made, as when the delegate starting it already works in one, the session keeps no state directory or the working directory is not in a git repository, the delegate does not start, and the planner is told why. A turn you run yourself with /agent is yours, so it works in your working tree and says so. A delegate in a checkout keeps no memory, so a definition with both keeps its memory only in a /agent turn, and bravebot says so when it loads the definition.

A checkout shares its branches, tags and remote-tracking refs with your working tree and with every other checkout, as any git worktree does. A git fetch a delegate runs in one moves origin/main in your working tree too, and several fetching at once can fail. The planner and each delegate are told this, and the planner is told to have the fetch done once rather than by each delegate.

The commands that bring a checkout's work back into your tree are not built yet. A checkout a delegate wrote in stays where it is until you remove it with /checkouts remove, and /status and /checkouts list each one the session has kept.

Which one wins​

Your own directory is read first and the project second, so a project definition of the same name replaces yours, which is the same "most specific wins" the trust map uses for paths. Two files in one directory resolve by file name, so which is live is the same on every machine.

It wins about what the definition is for, and never about what it may do. The project's file takes over the description, the body, the model, the skills, the rounds and the memory, the rounds held to the ceiling of the kind it is loaded as. The kind is the narrower of the two, and the tools and mcpServers lists are met name by name, so a checkout you vouched for cannot turn a reader you wrote into a worker, and cannot hand back a tool or a server your own lines took away. A checkout is met the same way: either file asking gives one, so the project's can add one and cannot take yours away. Vouching for a project is a decision about the project, not one about a name you had already defined. The same holds for two files of one name in one directory, since which of those is live is only a matter of file name. Whatever the later file asked for and did not get is said, with the one that cut it down beside it:

.bravebot/agents/rule-reviewer.md does not widen ~/.bravebot/agents/rule-reviewer.md: it names kind worker and is loaded as a reader

Trust​

The same rule skills follow, for the same reason and with more riding on it: ~/.bravebot/agents is trusted because it is your own directory. A project's .bravebot/agents is workspace content, read through the trust map, so it loads when you vouched for the directory and is left out when you did not:

2 delegate definitions in .bravebot/agents were not loaded: this directory is not trusted

Counted and never named, because a file in a project nobody vouched for can be given a name that reads like an instruction. A definition that fails the gate is dropped entirely rather than quarantined: an instruction is either followed or absent.

A definition's body is the whole of what a second agent is told it is. A skill's body is guidance a turn may follow; this is more than that. Read one before you install it, the way you would read the configuration file that picks your model.