Skip to the content.

A busybees staff has five roles. Each session is a fresh non-interactive run of the role’s agent with that role’s system prompt, so a role remembers nothing between sessions beyond its notes file and what is visible on GitHub. This page says what each role is: what it reads, what it may do, what it reports, and which roles.<name>.* keys shape it. What happens to an issue on its way through the factory, the labels, the queues and the review loop, is on workflow.md.

Concurrency model

Role Instances Runs when
product_manager singleton unread mail; scheduler.product_manager_interval (default 1h) elapsed since its last run, the first run being immediate; a person approved a proposal by removing bees:proposal; a feature whose recorded sub-issues have all closed; a fresh bees:feedback or bees:feature issue, one a person created or commented on since the product manager last replied (on a bees:proposal or bees:planning issue only a comment counts)
project_manager singleton an issue in bees:triage, or unread mail
developer pool of scheduler.max_developers workers (default 1) a bees:ready issue is waiting, or an issue in bees:in-progress, bees:review or bees:approved has a worker to resume. A ready issue that already has a pull request goes before new work
reviewer one per developer worker, in turn with it; one per requested review, in a developer slot the worker’s developer session opened or updated a pull request; a check on that pull request failed, before the first review or, with auto_merge, after approval; a person put bees:review-requested on a pull request; with scheduler.review_assigned_prs, a pull request the factory did not write is in the filter at a head no review has looked at
qa singleton unread mail; or scheduler.qa_interval (default 30m) elapsed and something was merged since its last run, the first run being immediate

A developer worker owns one issue at a time and runs developer, reviewer, developer, and so on for it, one session at a time. A review a person asks for with bees:review-requested, or by scheduler.review_assigned_prs, runs outside any worker and takes a slot from the same scheduler.max_developers pool, so that number bounds every reviewer session too. A singleton runs one session at a time and waits at least scheduler.poll_interval (default 5m) after a session ends before starting the next; after a session that reported failed it waits five poll intervals.

Every role reports how a session ended with the done tool. A session that ends without an outcome, runs past the role’s timeout or exits with an error counts as failed. What the orchestrator does with a failure is under Escalation; which ready issue a free developer takes is under Sizing and Priority.

Common ground

Every role’s system prompt starts with the same preamble: what busybees is, and that the product’s purpose lives in the repository rather than in the prompt; the visibility filter; the label state machine; the tools; the mailbox; how to report an outcome; the notes file; the environment (repository, working directory, branch, state directory, session directory); how to learn the project; and the ground rules. bees prompts show <role> --rendered prints the whole system prompt a role receives for this project.

People outrank the prompt. Anything a person writes, in an issue, on a pull request or in mail from human, is authoritative for every role. Mail from human is a direction, not a question: the role follows it even where its prompt says otherwise, and says in its outcome what it did about it. How your comments reach a session is under Giving the developer feedback and Commenting on the issue.

Learning the project. busybees tells a role nothing about how to build, test or run the product. The role reads the repository’s README, CONTRIBUTING, CLAUDE.md, Makefile and CI configuration, and records the commands and gotchas in its notes file. The prompt adds that documentation which is missing or wrong is worth an issue.

The comment marker. People and bees share one GitHub account unless [github] gives the factory its own, so every comment a role posts ends with the line <!-- bees:<role> -->, invisible when rendered. The comment tool appends it; a role posting through gh (the product manager closing an issue, the developer replying to an inline review comment) writes it itself. The orchestrator reads the marker to tell a bee’s comment from a person’s, and with [github] set it also counts a comment by the configured login as a bee’s, and warns in the log about a comment of its own that ends a session without one. The marker is required either way.

Ground rules. Stay in your role. Do not merge pull requests. Do not push to the default branch. Do not remove the bees label from anything. Write for a person who has not seen the conversation. Say so in the outcome note when you could not finish.

Tools. The factory’s own operations and the GitHub actions a role performs are MCP tools, served to every session by the built-in bees server. Eleven go to every role: mail_send, mail_list, issue_create, issue_link, issue_view, pr_view, comment, report_factory_error, notes_read, notes_write and done. Five are role-scoped: issue_edit_body (both managers), issue_set_state (project manager), issue_question (product manager), submit_review (reviewer, for a requested review only) and file_bug (QA). The tools enforce the factory’s rules: no issue or pull request outside the filter can be read or written, comment and submit_review append the marker, issue_edit_body refuses a feature or feedback issue for anyone but the product manager, issue_set_state only moves an issue out of bees:triage, and file_bug refuses a bug the repository already reports. Its duplicate check reads past the filter, because a duplicate can be an issue a person filed without the factory’s label. A tool a role is not offered is one it may not use, and the prompt tells it not to reach for gh instead. gh, already authenticated, is for everything without a tool: gh pr create, gh pr diff, gh issue close, gh api. Issues are always created with issue_create (QA’s bugs with file_bug), which applies the filter labels and assignee, and pull requests with the gh pr create flags the prompt spells out. bees mcp tools <role> prints a role’s tool set; the arguments and the matching bees commands are under bees mcp serve. report_factory_error is for an error the factory itself caused, not the product: with scheduler.report_factory_errors on, the prompt tells every role to strip repository names, tokens, paths and people from the report and the tool queues it under <state_dir>/feedback/. Every full pass files the queue against the busybees repository, commenting on the issue a report duplicates instead of opening a second one (see the scheduler loop); off, the tool records nothing and says so, and the prompt does not mention it.

Mail. Roles talk to each other only through the local mailbox, never through GitHub comments. Every message carries the issue and/or pull request it is about, so it reaches the session working on that item. The unread mail addressed to a role is rendered into its task prompt and marked read when the session ends. bees mail list reads the mailbox and bees mail send --from human --to <role> writes to a role. Who each role may write to is under the role below.

Notes files

A role’s only long-term memory is what the notes_read tool returns and what notes_write replaces it with: nothing renders notes into the prompt, so the role is told to call notes_read at the start of a session, before anything else, and notes_write before it finishes, with the complete text, headings included: decisions, conventions, gotchas, what it tested. A first read creates the notes with a # <role> notes heading and the four sections roles keep their notes under: Project facts (how to build, test and run the project), Conventions, Decisions and Open questions. Anything else goes under a heading of the role’s choosing. Developer workers share one set of notes.

Nothing curates the notes behind a role’s back. Every scheduler.notes_consolidate_every sessions (default 10), or sooner once they are larger than scheduler.notes_max_bytes (default 32768), the task prompt asks the session to rewrite them into those sections on top of its normal work: merge duplicates, drop what is stale or contradicted, keep decisions, commands and gotchas. The counters live in <state_dir>/<role>.json; the size bees status shows and this trigger reads is measured in the backend notes.backend selects, so it is the size of what notes_read returns.

With the default notes.backend = "file", notes_read and notes_write act on <state_dir>/notes/<role>.md, and editing that file directly is the most direct way to steer a role: write the product vision into notes/product_manager.md, coding conventions into notes/developer.md, or “always run the e2e suite” into notes/reviewer.md, and the next session reads it through notes_read. bees notes does it without hunting for the file. With notes.backend = "neo4j", notes_read and notes_write act on Neo4j Agent Memory instead, and bees notes show|edit|reset|add keep acting on the file, which no session then reads.

bees notes show reviewer
bees notes add reviewer "Always run the e2e suite before approving."
bees notes edit developer
bees notes reset qa        # archive the file and start a fresh one

product_manager

Owns the what and the why: the product vision, the feature issues, and the answers to the project manager’s product questions. It runs against a detached checkout of the default branch and is told to read the codebase and the README to understand what exists.

Reads. The open milestones (number, title, open and closed counts, description), read only. Every open feature issue in a table: milestone, sub-issue progress as completed/total, whether it is a proposal, whether it is waiting on a person. The fresh feature issues in full, with every comment. Its own proposals awaiting a person’s approval, the issues in planning with a person, and the ones a person has agreed, each group in a section of its own. The features whose work is done, every sub-issue closed since the last run. Every open work item: state, kind, the parent feature it is a sub-issue of (- when none), milestone, title. The open pull requests. The fresh feedback issues in full. Unread mail. Its notes. Each list is a snapshot taken through the filter when the session started, and the prompt tells it to confirm with issue_view or gh before creating, closing or commenting on anything.

Does. Creates feature issues with issue_create (feature: true, plus related: <feedback issue> when the feature comes from feedback), describing a user-visible outcome rather than an implementation. Breaks a feature into work items with issue_create (parent: <feature>; bug: true for a bug; blocked_by for an order; a size label in labels as a hint the project manager confirms), then comments the list on the feature. Attaches loose work items to their feature with issue_link, once a pass, reading the Parent column of its task. Rewrites feature and feedback bodies with issue_edit_body, which no other role may. Asks a person a question on the issue with comment and issue_question (waiting: true), starting the comment with the mentions from scheduler.notify when it is set; the orchestrator removes the label when the person answers, and waiting: false only withdraws a question. Replies on every feedback issue it acts on, and routes a ready-to-build ask straight to a work item (issue_create, related: <feedback issue>, carrying bees:priority over with gh issue edit --add-label when a person put it there) rather than writing a feature around it. Closes feature and feedback issues with gh issue close, the one action with no tool, writing the marker into the closing comment by hand. Never creates, edits or closes a milestone: it reads them as a priority signal and says in a reply when it thinks one is wrong or missing. The only milestone it names is for a feature spawned from an agreed design (issue_create’s milestone), and always an existing one. It searches before it creates and keeps the backlog small; a full ready queue is a reason to create less.

Proposals, planning mode, questions, feedback and finished features are the product manager’s half of Talking to the product manager and Features, sub-issues and milestones. In short: a feature it writes is a proposal it may refine but not break down until a person removes bees:proposal, and issue_create and issue_link refuse the breakdown while the label is there, unless scheduler.feature_proposals = false, as the slop-factory config template does, which makes the features it writes approved on creation. An issue in bees:planning is a conversation it replies to on the issue and creates nothing from. bees:planned is an agreement it writes into the body as a ## Decisions section and then acts on without reopening it. When the agreed design covers more than one coherent outcome it spawns a feature issue per outcome, not just a refinement of the issue it planned on: each in the existing open milestone that fits the design’s phasing, ordered with blocked_by, as From an agreed design to several features describes. A feature whose work is done is one yes-or-no decision: close it, or say on the issue what is missing and create work items for exactly that.

Mail. Receives the project manager’s product questions, QA’s reports and mail from human. Writes to project_manager only, and only when the answer changes what the project manager does, because unread mail on its own starts a project manager session.

Outcomes. done with a one-line summary, idle when the pass found nothing to do, failed when it could not run the pass at all. The orchestrator marks the delivered mail read and records the run time, which restarts the product_manager_interval clock. A run woken by the clock rather than by an event still works the agreed section and the loose-work-item check before it reports idle.

Configuration. roles.product_manager takes the common keys and one of its own: min_issue_size, the smallest work item it aims for when it breaks a feature down (unset, it splits at whatever boundaries the work has). Set anywhere else, it is a load error. See configuration.md.

project_manager

Turns work items into work a developer can execute without guessing, and answers developers’ questions. It runs against a detached checkout of the default branch.

Reads. The first scheduler.triage_batch_size (default 5) work items in bees:triage in full, each with its parent feature and the open issues it is blocked by. The rest of the triage queue in a table of its own, which the prompt says is triage work too. A table of every other visible issue: state, kind, blockers, milestone, title. The open pull requests. Unread mail. Its notes.

Does. Rewrites a work item’s body with issue_edit_body: context, scope in and out, acceptance criteria, pointers to code, testing expectations, keeping the author’s meaning. Splits an item too big for one pull request with issue_create (ready: true, parent: <feature>, or related: <original> when there is no parent; blocked_by for the order) and closes the original with a comment listing the parts. Moves a refined item to bees:ready with issue_set_state (state: ready, size: <xs|s|m|l|xl>), which sets both labels in one edit. The size is required, and an item that would size above roles.developer.max_size is split instead of labelled. Asks the product manager for a product decision by mail and moves the item to bees:blocked with the same tool; a work item that is really a direction rather than a piece of work goes the same way, instead of getting invented acceptance criteria. Closes an invalid or duplicate item with a comment naming its replacement, closing the one the work is not attached to. Checks a bug still happens on the default branch before moving it to bees:ready. Writes Blocked by #N into an item that depends on another and still moves it to bees:ready; the scheduler holds it. Adds bees:priority in one case only, a work item that unblocks the factory itself, with gh issue edit --add-label. It never edits feature or feedback issues (issue_edit_body refuses them), never touches milestones, and never moves a bees:ready issue back to bees:triage to reorder the queue.

The states it moves, how sizes are read and how a blocked question comes back are under The label state machine, Sizing, Dependencies and Questions.

Mail. Receives developers’ questions, a person’s comments on an issue it blocked out of triage (delivered as mail from human), and any other mail from human. Writes to product_manager for product decisions and to developer for answers, always with issue. The prompt puts mail first, since developers are blocked on it, and asks for a decision rather than options. A direction from human that holds work back keeps the items in bees:triage, with the hold written into the body.

Outcomes. done, idle, failed. The orchestrator marks its mail read and records the run. An answer it sent lifts bees:blocked on the pass its finished session wakes, and a question it sent must carry the issue number or the issue waits for ever.

developer

Implements one issue on a dedicated branch and opens a pull request for it. scheduler.max_developers workers run at once, each owning one issue.

Reads. The issue with its labels, milestone and every comment; the parent feature’s number and title when the issue is a sub-issue; the existing pull request on a later round; the round number and scheduler.max_review_rounds; unread mail about the issue or the pull request; its notes. The mail is where every direction arrives: the project manager’s answers, the reviewer’s feedback, a person’s review of the pull request (from human, with comment ids and the exact gh reply commands), a person’s comment on the issue while it is in flight (from human too), and the orchestrator’s request to bring the branch up to date when the pull request conflicts with the default branch.

It runs in a git worktree on bees/issue-N (the prefix is project.branch_prefix), based on the default branch. The session environment sets push.autoSetupRemote=true and push.default=current through GIT_CONFIG_* variables, so a plain git push works and the clone’s own git configuration is untouched. With [github] set it also carries the factory’s token, commit identity and a git credential helper, so what it commits and opens is the factory’s rather than the machine owner’s, and an https push authenticates as the factory too. commit_flags tells it to add flags to every git commit it makes.

Does. Explores the codebase, implements on the branch in small commits, adds or updates tests and runs the test-suite the way the repository documents. Before handing over it checks the change the way the reviewer will: runs the repository’s own lint and test commands and records them in its notes, undoes its fix to confirm the test it added fails without it, and greps for every claim the change makes false rather than for the sentence it edited. It merges the default branch into the branch before every push, on every round, and runs the tests again afterwards. It pushes and opens the pull request with gh pr create (body with Closes #N, a summary and how it was tested), or pushes to the existing one and rewrites its description. Where the issue leaves a choice it makes one, implements it and records it in the pull request under a heading of its own, for the reviewer to rule on.

On a review round it addresses every point or says in the pull request why not; the pull request is the conversation, never mail back to the reviewer. A person’s review comment gets a reply on GitHub, an inline one through gh api .../replies with the marker written by hand, a review or conversation comment through comment. When a person’s request conflicts with the issue or the reviewer, the person wins and the pull request says so. A person’s comment on the issue is answered on the issue with comment. Bugs outside the issue’s scope are filed with issue_create (bug: true, related: <issue>), never fixed in passing. It never changes labels and never pushes to the default branch.

A later round. The second and later developer sessions of one worker continue the conversation of the previous one (claude --resume, or --session for opencode), so the developer handed feedback still knows the codebase and its own change. The task prompt is rebuilt for the round all the same. A worker started after a restart of bees run begins a fresh conversation, and so does every round of a codex role, which has no resume. See The developer worker.

A session that was interrupted. When the session working the issue never finished, because the scheduler died with it or a person stopped it (bees kill, the live view’s k, or a second ctrl-c), the next session is told so first: how far the interrupted one got, where its transcript is, and whether it was stopped on purpose. The developer is told the branch may carry commits, uncommitted edits or a pull request the interrupted session never reported, and to carry on from them. A reviewer in the same position is told its round reported no verdict and starts over, and that the interrupted session may already have sent mail or posted on GitHub. Only the interrupted role’s first session is told, and bees status marks the worker resumed. See The developer worker.

Mail. Writes to project_manager only, and only when no reading of the issue is safe: the issue contradicts itself about something the repository cannot settle, or the wrong choice would throw the implementation away. It then sends one message, stops without implementing and reports question. Everything else is a choice it makes and records in the pull request.

Outcomes.

Status What the orchestrator does
pr-opened, pr-updated (with pr) Locates the pull request by number, else by branch; makes it match the filter (the bees label, plus the assignee and milestone when configured); moves the issue to bees:review; reads the checks (pre-review checks) and runs the reviewer. No such pull request: escalate.
question Checks that a message to the project manager was sent during the session, then labels the issue bees:blocked and frees the worker. No message: escalate.
failed, or no outcome Escalates with the note, posted on the issue as the comment a person reads. The prompt reserves it for when neither a partial pull request nor a question can move the issue forward.

With the reviewer disabled, pr-opened and pr-updated approve the pull request at once, and with auto_merge the worker goes on to the checks stage.

Configuration. roles.developer takes the common keys and a few of its own: commit_flags, max_size (default l, the largest work item a developer takes), model_by_size (a model per size label), the best-of-N keys (best_of_n_by_size and the model and prompt overrides for the attempts and the assembler) and the mixture-of-experts keys (moe_experts_by_size, moe_experts, moe_assembler_model and moe_assembler_prompt). Set anywhere else, they are a load error. See configuration.md, best of N and mixture of experts. How a fan-out and its assembler session run is on workflow.md and workflow.md.

An expert is not a role of its own: it is a developer session running one expert’s prompt and model, with the developer’s task, tools and outcomes, and so is the assembler that combines what the experts did. bees config show developer prints the keys that pick them.

reviewer

Reviews one pull request and decides whether it is ready to merge. It also owns the checks and the merge: auto_merge and its companions are roles.reviewer keys.

A review a person asks for by putting bees:review-requested on any visible pull request, or that scheduler.review_assigned_prs asks for on a pull request the factory did not write, is a session of the same role in a second mode, with no issue and no developer behind it, whose verdict is one GitHub review on the pull request: see Requested reviews below. The rest of this section describes the review of a developer’s pull request.

Reads. The pull request (title, body, branches, author); the issue with its body; the findings the review pipeline already produced for it, most severe first, with the size it judged the change and the angles that size ran; the pull request’s checks as read just before the first review, or a line saying nothing was verified; unread mail addressed to reviewer about the issue or the pull request; the round number and scheduler.max_review_rounds; its notes. It runs in the developer’s worktree for that issue, brought up to the latest push, so pr_view reads the change in context.

Does. Reads the pull request with pr_view for anything a person already said on it, which outranks the issue, the findings and these instructions. Posts the findings as one comment review with submit_review, the verdict line first and then every finding as the judge produced it: nothing dropped, nothing added and nothing softened, because a finding that should not be there is fixed in the angle or judge that raised it, not in this session. Verifying that the change builds and passes is CI’s job: the prompt tells it not to re-run the repository’s test-suite. It decides the verdict itself: a high finding always needs fixing before the merge, an info one never does, and between them it judges. A finding about a defect the change did not introduce goes in the review like the rest and is also worth an issue (issue_create, bug: true, related: <issue>), without blocking the pull request on it. On a developer’s pull request it does not submit an approval or a request for changes on GitHub, which refuses both from a pull request’s own author, push to the branch or change labels. Nothing it writes reaches the person who merges except its outcome note, so the note carries how many findings there were, what it chose not to block on, and, when no check was reported, that nothing was verified for it.

Mail. Writes to developer only: one message per round, with pr and issue, the verdict line and every finding that needs fixing, each with its file and line and what it expects instead. An approval sends no mail, because the developer’s work on the issue is over and no session is left to read it: anything the developer should know goes in the note, anything worth doing in an issue. It receives mail addressed to reviewer, in practice from a person (bees mail send --from human --to reviewer), in a review session and a checks-mode session alike, and a copy of any comment a person writes on the issue while it is in bees:review.

Outcomes.

Status What the orchestrator does
approved Nothing in the list needed fixing. Labels the pull request and the issue bees:approved and requests a review from the people in scheduler.notify. Without auto_merge the worker is freed and a person merges; with it, the worker enters the checks stage (Checks mode). A pull request stacked on another one (scheduler.stacked_prs) is labelled only once the one beneath it is approved; the worker waits for that first.
changes-requested Checks that feedback was mailed to the developer during the session (none: escalate). On the last round: escalate. Otherwise moves the issue back to bees:in-progress and runs the developer with the feedback in its mail.
failed, or no outcome Escalates.

The reviewer is told when it is on the final round, so it still requests changes honestly and lets the orchestrator escalate. The loop as a whole, max_review_rounds and what the person merging sees are under Review and Merging.

Requested reviews (bees:review-requested)

A person asks for a review of any pull request the factory can see, their own or anyone’s, by putting bees:review-requested on it, and scheduler.review_assigned_prs asks for the same review of every pull request in the filter the factory did not write, once per head commit and without the label; how each is picked up is under Asking for a review of any pull request. The session is the reviewer role with its prompts switched to this mode, and it is the same session whichever of the two asked for it.

Reads. The pull request (title, body, branches, author); a statement that there is no issue and no acceptance criteria, so the description and the diff are the whole brief and criteria the description does not state are not to be invented; the login the factory acts as, with a sentence saying whether that is the pull request’s author (with no [github] table it is told to find out with gh api user); unread mail addressed to reviewer about the pull request; its notes; and the findings the same review pipeline produced for it, the brief’s distiller reading the description in place of an issue. It runs in a read-only checkout of the head branch, or of the default branch when the remote does not have it.

Does. Reads the pull request with pr_view as in a review, then submits exactly one GitHub review with submit_review: approve when nothing in the list needs fixing, request-changes when something does, and comment in place of approve when the pull request’s author is the login the factory acts as, because GitHub refuses an approval from a pull request’s own author, saying in the body why it is a comment. The body carries the verdict line and every finding as the judge produced it, and ends with the <!-- bees:reviewer --> marker. It posts nothing else on the pull request, pushes nothing and changes no label.

Mail. Sends none: there is no developer on the pull request. It receives mail addressed to reviewer about the pull request.

Outcomes. approved after an approval or a comment in its place, changes-requested after a request for changes. Both are checked against the pull request’s reviews before they count as a pass: a verdict GitHub holds no matching review for is a failure (see What the orchestrator checks). Nothing is labelled either way, because there is no issue. failed, or no outcome, is logged and the pull request is not tried again for five poll intervals; a person adds the label again to ask for another pass, and under review_assigned_prs a push does the same.

Review pipeline (roles.reviewer.angles)

The review itself runs before the reviewer session that posts it: one session, the distiller, reads the pull request’s context and writes a brief of the change, its size (xs to xl, judged from the scope and the risk of the diff) and its acceptance criteria; one session per angle the size calls for reads the brief and the diff and looks for problems from that angle alone; and the judge, deterministic code and not a session, merges what the angles found into one list, most severe first. The brief and angle sessions run read-only, with no MCP server and no tool that writes, runs or fetches, in a local clone of the pull request’s checkout; only the judge step is a factory session, and it does not review the change again: it posts the list submit_review gives it and decides the verdict from it (see reviewer above). What each step costs is entered in the ledger under the round’s name.

Angle What it looks for
quick_general One light pass over the change as a whole, for what a careful reader catches on one read
general A thorough pass over the change as a whole: wrong logic, a dropped error, duplicated code, and a line that breaks a rule the project wrote down
docs A comment or doc comment the change made false, and prose the change wrote that the code does not bear out
test_coverage Behaviour with no test on it, a test that would pass with the change undone, and a document the change made false
acceptance_criteria A criterion from the brief the change does not meet, or meets only in part, and a behaviour change nothing asked for
side_effects A caller the change did not update, an invariant it no longer keeps, and a claim elsewhere in the repository it made false

roles.reviewer.angles replaces, for one size at a time, the angles a change of that size is reviewed from; a size it does not name keeps the built-in list. brief_model, angle_models and judge_model override model for the brief, for one angle each and for the judge, one session at a time. See configuration.md for the keys, their defaults and their built-in per-size lists.

An angle that fails is named in the reviewer session’s task and the rest are judged; a review where the brief or every angle failed cannot be posted and escalates the issue with the reason instead of running the reviewer session.

Pre-review checks (pre_review_checks, on by default)

Before the first review, the developer worker reads the pull request’s checks, waiting checks_wait, polling every checks_poll_interval and giving up after pre_review_checks_timeout (default 10 minutes). Green, and the reviewer’s prompt lists the checks under ## Required checks and says CI is green. Still pending at the timeout, or no check reported at all, and the review happens anyway, with the reviewer told nothing was verified for it and to say so in its note. A failing check goes to checks mode before any review, and the reviewer sees the pull request once it is green. The read happens once per pull request: a later round has no checks section, because the checks that were read describe a head the developer has since replaced. Where the stage sits in the review loop is under Before the review.

[roles.reviewer]
pre_review_checks = false    # straight from the developer to the reviewer

Checks mode (a failing check)

A failing check, before the first review or after approval with auto_merge, gets the reviewer a different kind of session: a diagnosis, not a review. The session runs with BEES_REVIEW_MODE=checks in its environment, and there is no review pipeline before it: it has no findings to post.

Reads. The pull request, the issue, the failing checks (name, workflow, bucket, description, details link), the fix round and max_check_fix_rounds, unread mail, its notes.

Does. Finds out what failed without assuming a CI system: from each check’s details link and description, from gh pr checks, from the repository’s documentation and its own notes; with gh run view --log-failed when the link is a GitHub Actions run; and, only when no log is reachable, by reproducing the failure on the branch, to name the error rather than to verify the change. It records how to read this project’s CI in its notes. Then it distils the main error, its cause, the file and line where possible, and a command that reproduces it into one mail to the developer.

Status What the orchestrator does
changes-requested Checks the mail was sent (none: escalate), then runs the developer, whose pr-updated goes straight back to the stage that found the failure, pre-review or post-approval. No review round is spent.
approved “Wait again”: the checks were already green when the reviewer looked, or it re-ran a failure it judged unrelated (infrastructure, flakiness), which it does at most once. The orchestrator waits checks_wait and polls once more.
failed, or no outcome Escalates.

Fix rounds are counted in <state_dir>/issues/<n>.json and capped by max_check_fix_rounds (default 2). Pre-review and post-approval rounds share the counter, and neither counts against max_review_rounds. The reviewer is told when it is on the last fix round. What the post-approval stage polls, when it merges and when it escalates is under Merging; the keys are under configuration.md.

[roles.reviewer]
auto_merge = true
merge_method = "squash"
max_check_fix_rounds = 3

qa

Tests the product as a user would, from the default branch, and turns what it finds into bug reports and a report to the product manager. It runs against a detached checkout of the default branch, so it only sees merged work.

Reads. When it last ran, and the pull requests merged since then (title, body, closing issues); the open bees:bug issues, to avoid duplicates; unread mail addressed to qa; its notes. The first run looks back seven days.

Does. Works out from the repository’s documentation and its notes how to install dependencies, run the test-suite and exercise the product, and records which commands are safe. What “exercise it” means follows from the product: a service is launched and driven, a command-line tool or a library is run the way its documentation tells a user to. It never starts anything that acts on the real world for it, such as a deploy, a job runner or a command that spends money or writes to the live project the product manages, and uses a sandbox, a throwaway configuration or a dry-run flag instead. It verifies each merged pull request against its issue and explores around it for regressions. When the merged list is long it spends its time on what a user touches and says in its report which entries it only skimmed.

QA judges what the product does, not how the code is written; that is the reviewer’s job. A clean batch is a good result: QA files nothing and says so. It files only what it saw the product do wrong itself, with the command it ran and the output it got, as file_bug (title, body, related: <issue the merged pull request closed>, omitted when the bug is not tied to a recent change), with reproduction steps, expected against actual behaviour, and severity. file_bug scores the report against every issue in the repository, closed as well as open, and files nothing when one of them already looks like it: the candidates come back ranked instead. QA comments on an open duplicate with comment, and files with override: true, saying in the body why, when the match is a closed issue it has reproduced again or none of the candidates is the same defect. A broken environment is filed once however many merged pull requests it spoils. What it may file directly is a bug report or a small work item within the existing design. Anything asking for new scope goes to the product manager by mail, and QA never opens a feature issue itself.

Mail. Writes to product_manager only: one report per session saying what was tested, what works, the bugs filed and product-level observations, sent even when nothing was found and skipped only when QA could not test at all. Receives mail addressed to qa, in practice from a person (bees mail send --from human --to qa); unread mail starts a QA run on the next pass whatever qa_interval says.

Outcomes. done with a one-line summary, failed when it could not test, with why. The orchestrator records the run time either way, and checks that done has the report behind it: a session that sent none is a failure (see What the orchestrator checks). When QA runs and what it looks at is under QA.

Customising a role

Everything is in bees.toml. [global] applies to every role and [roles.<name>] layers on top of it:

[global]
prompt = "Follow CONVENTIONS.md. Prefer small PRs."
skills = ["https://github.com/acme/skills#skills/repo-conventions"]
model = "opus"
fallback_model = "sonnet"
shell = "/bin/bash"
[global.env]
CI = "true"

[roles.reviewer]
prompt = "Block any PR that lowers test coverage."
model = "sonnet"
max_turns = 60

[roles.developer]
skills = ["https://github.com/acme/skills#skills/tdd"]
commit_flags = "--gpg-sign --signoff"
[roles.developer.mcp.playwright]
command = "npx"
args = ["-y", "@playwright/mcp"]

[roles.qa]
prompt_file = "docs/qa-checklist.md"

bees config show <role> prints the result of the merge. See configuration.md for every key.

Disabling a role

[roles.qa]
enabled = false

A disabled role is never dispatched. bees run --roles developer,reviewer restricts one run the same way without editing the file. What each one costs:

enabled is a role key; under [global] it is a load error.