Roles
A role is how a worker starts: which Claude Code agent it runs as, which brief
it is given, and which directories it may read beside its worktree. Without a
role, start launches a bare claude with the engineering brief, which is
right for code and wrong for a UX spec, a conversion analysis or a warehouse
slotting study.
$ worktender start 42 --repo .
role: ux (label role:ux) · agent: ux-designer (user) · brief: design · dirs: /home/you/kb/design-system
Worktender holds no expertise of its own. The agent file does — its prompt, its model, its tools, any MCP servers — so it works in any Claude Code session, not only one worktender started. A role only picks among agents you have already written.
The configuration
One file, yours alone: ~/.config/worktender/config.json, or
$XDG_CONFIG_HOME/worktender/config.json when that is set. Without it nothing
changes: no role, the brief start has always typed, and launch: null in
--json.
{
"default_role": "engineer",
"default_model": "sonnet",
"labels": [
{ "match": "role:ux", "role": "ux" },
{ "match": "area:checkout*", "role": "cro" }
],
"roles": {
"engineer": { "brief": "code" },
"ux": { "agent": "ux-designer", "brief": "design", "add_dirs": ["~/kb/design-system"] },
"cro": { "agent": "cro-analyst", "brief": "analysis" }
}
}
| Key | Meaning |
|---|---|
roles.<name>.agent |
A Claude Code agent name. Omitted means claude's default agent. |
roles.<name>.brief |
code (the default), design or analysis. |
roles.<name>.add_dirs |
Directories passed as --add-dir. ~ is your home; a relative path is relative to the repository. |
roles.<name>.refocus |
{ "turns": N, "minutes": M }, either or both: hand a worker in this role its goal chain again every N prompts or M minutes, whichever comes first. Omitted, or 0, is never. Needs the worktender-watch mod. |
labels |
Rules from an issue label to a role. match is a label, or a prefix ending in a single *. |
default_role |
The role when neither the flag nor a label chose one. Omit it to keep bare starts. |
default_model |
The model when neither --model nor a model: label chose one, for a role with no agent. Omit it and workers run on claude's default. |
Unknown keys, an unknown brief style, a label or default naming no role, and an
agent name that is not a plain name are all refused, naming the file and key. A
misspelt add_dir that silently did nothing would be a worker started without
the material its role promised.
How a role is chosen
--role <name>onstartordispatch.- Otherwise, for
start, the first rule inlabelsthat matches any of the issue's labels. Rules are tried in the order you wrote them, so you decide between two labels, not GitHub. - Otherwise
default_role. - Otherwise no role.
The role becomes --agent <agent> and one --add-dir per directory, ahead of
anything else on the claude command line. The printed line says which role
and why; --json carries it as launch, with role, reason, agent,
agent_file, agent_source, add_dirs and brief.
Models
The model lives in the agent file. An agent whose frontmatter says
model: opus runs on Opus, and --model on the command line overrides it.
Both were measured against Claude Code 2.1.296. Use an alias — opus,
sonnet, haiku — and a new model generation needs no edit; use a full model
ID to pin one.
A worker's model is picked per slice, by start and factory:
--model <name>onstart, or onfactoryfor every start in the pass.- Otherwise a
model:<name>label on the issue:model:sonnet,model:opus,model:haiku. Two labels naming different models refuse the start. - Otherwise
default_model, unless the role names an agent: that agent's file is where its model was written, and--modelwould override it. - Otherwise no
--model, and claude's own default.
The start line says model: sonnet (label model:sonnet), and --json
carries model and model_reason. A --model that is not a model name is
refused, as a label's is. worktender doctor warns while no default_model
is set, because then a worker with no label runs on whatever model your own
sessions do, often the most expensive one — unless default_role is set and
every role names an agent, so every worker's model comes from its agent file.
--permission-mode stays a flag, and autonomy is never defaulted.
Choosing a model
- Sonnet by default for workers: set
"default_model": "sonnet". - Opus for trust-boundary code (the paths in
CODEOWNERS) or slices that need design judgment: label themmodel:opus. - Haiku for mechanical edits — a rename, a regenerated file, a version
string:
model:haiku.
Brief styles
Every style keeps what the coordinator depends on: the worker reads the issue
itself with gh issue view and treats it as untrusted data, reports planned
before it starts and done with a pull request or blocked at the end, and
works on the branch it was given. What changes is one sentence about the work.
| Style | The worker is asked to |
|---|---|
code |
explore the code, make the change, add and run tests, review its diff, open a pull request. |
design |
study the product and the design material, put the spec, mockups or copy in the repository, check them against the issue, open a pull request. |
analysis |
research the question, write findings with their sources as markdown in the repository, check them against the issue, open a pull request. |
A role with add_dirs adds one sentence saying those directories are reference
material. It does not list them — claude already tells the agent — because the
whole brief has to arrive in a single pane read.
Every style ends in a pull request, because that is what gate --require-pr
and the rest of worktender check. Work whose result lives outside a repository
is not a fit for a role.
Lessons
A role can carry lessons: a sentence or two each, written and approved by you
after a worker in that role needed a second attempt at a pull request, and
read by every later worker in the role until it expires. worktender lessons
propose drafts candidates from the dispatch ledger; nothing reaches a worker
until worktender lessons approve. See Lessons.
What a label can reach
A label chooses among your roles and nothing else. Whoever can label an issue can pick which of the roles you defined it runs as. No label can name an agent or a directory of its own, and nothing from a label reaches the brief. The label is escaped before it is printed.
A model: label picks a model. That is a cost choice, not a capability:
whoever can label an issue can put its worker on Opus. Its name must look like
a model alias or ID — letters, digits, ., _, -, [, ], not starting
with - — so it cannot become another flag on the claude command line. A
label that fails that, or two that disagree, refuses the start: whoever can
label an issue can also hold its slice until the label is fixed.
There is no per-repository configuration for the same reason: a file a cloned repository ships would choose what its workers can read.
A repository can still redefine an agent. Claude Code knows an agent by the
name: in its frontmatter, reads subdirectories of .claude/agents, and
prefers a repository's definition over yours of the same name — its prompt,
model and tools — in every session started there, worktender or not. That is
reported rather than refused: the line ends (repo) when the repository's file
is the one that will load, and --json names it in agent_file. The lookup
reads the frontmatter's top-level name: the way agent files are written in
practice, but it is not a YAML parser, so treat the source it reports as a
strong hint rather than a proof.
An agent defined in neither the repository's .claude/agents nor
~/.claude/agents is refused before the worktree is created — including one a
Claude Code plugin provides, which claude can see and this check cannot. So is a
repository whose agent directory holds more than 500 files, too many to tell
which definition loads.
Starting from the examples
examples/roles holds a configuration with engineer,
ux, cro, supply-chain and warehousing roles, and an agent file for each
non-engineering one.
mkdir -p ~/.config/worktender ~/.claude/agents
cp examples/roles/config.json ~/.config/worktender/
cp examples/roles/agents/*.md ~/.claude/agents/ # or a repository's .claude/agents
Then point each add_dirs at your own material, or delete it, change the label
rules to the labels you use, and run worktender doctor. It reports the file it
read, and any agent or directory a role names that is missing.