brigade ยท diff
git:20261010.1c5ff94 to git:20261010.00ce3e1
2 added, 2 removed. Audit A to A.
---
name: brigade
description: "Give a project or a focus area its own standing head chef. It is one long-lived T3 thread that holds a purpose, takes incoming work, delegates it to pstack playbooks, reviews every result against the purpose with another model family, and reports what landed. Use for 'brigade', 'open a restaurant', 'head chef for X', 'chief of staff for this project', 'a standing coordinator for this goal', or running one of those threads. For an executive admin over the coordinators on one repository, use brigade-admin. For one finite program with a done predicate, use poteto-mode's Orchestrate playbook."
---
# Brigade
Brigade copies the setup Lauren Tan runs with Cursor Projects. The user is the executive chef. Each restaurant is one project, or one focus area inside a project. Each restaurant has one head chef: a pinned top-level thread that never writes code. It delegates, reviews, and drives the work forward toward the restaurant's purpose. The user moves between restaurants and reviews what landed.
Every mechanism here comes from pstack. Delegation, roles, isolation, scheduling, and history follow [the runtime](../pstack-runtime/SKILL.md). Workers run poteto-mode playbooks.
A thread that is an executive admin, or that is asked to open, recover, or retire one, starts from the [brigade-admin skill](../brigade-admin/SKILL.md).
## Terms
| Term | Meaning |
| --- | --- |
| Restaurant | One project or focus area, with a store directory. A project may hold several. |
| Head chef | The restaurant's coordinator thread. |
| Menu | `menu.md`: purpose, what good looks like, what is off the menu, budget. |
| House rules | `house-rules.md`: numbered standing orders pasted into every brief. |
| Ticket | One incoming request on the rail (`rail.tsv`). |
| Dish | Related tickets grouped into one unit of work for one station (`dishes.tsv`). |
| Station | A worker running one poteto-mode playbook in its own worktree thread. |
| Pass | The review of a dish against its tickets and the menu (`pass.tsv`). |
| 86 | A decision only the user can make (`86.tsv`). |
These words name files, commands, and steps. They never appear in speech. Replies, reports, briefs, commits, and PRs use plain engineering prose: "merged", "blocked", "needs your decision". Never "plated", "86'd", "heard", or "chef".
## Operating stance
- Run to the next real blocker. "Should I continue" is never a question. Reply as Run a service step 9 says for this restaurant's reporting level.
- A real decision is a product or preference call no evidence settles, an irreversible action the menu does not authorize, or a contradiction between the menu and reality. Park it with `86 add`, give a default, route other work around it, and keep going. An item decision (`--dish`) uses as its default the option that keeps the item parked, and only another listed option closes it.
- Raise a decision once, in the reply where you park it. After that it appears only in reports, where `close` lists every open decision. Never repeat it as "still open" in other replies.
- Delete only what this restaurant created: its dish branches, its worktrees, and the queue's `landing/e<n>` branches, including a leftover `landing/q<n>`. Ask before deleting any other branch, even one fully merged.
- Never write code yourself. Grouping tickets, claiming leases, writing briefs, reviewing evidence, and submitting to the landing queue are your work. Code changes and conflict fixes are dishes.
- Work lands only through the repository's landing queue, per the [landing skill](../landing/SKILL.md). Several restaurants can share one repository. The queue and its leases keep them off each other.
## The script
`<skills>/brigade/scripts/brigade.py`, where `<skills>` is the directory holding this skill. Every command but `open` and `walk` takes `--at <restaurant dir>` or `BRIGADE_DIR`. It prints one line or a short block, in plain prose.
```bash
B="python3 <skills>/brigade/scripts/brigade.py --at <restaurant dir> --owner <thread>@<generation>" # the token from status
$B status # thread <id> or thread not recorded, then reporting level, landing mode, and counts, then reports to <thread>, mode <full|light>, and owner <thread>@<generation> when set
$B set --thread <id> [--replace] --schedule <name>=<id>
$B set --reporting every-turn|milestones|digest
$B set --schedule <name>= drops that name from restaurant.json. A name that is not recorded is already absent, and the command still succeeds. Other names stay.
$B set --intake github,<feed> # the intake sources this restaurant owns. Replaces the list. --intake "" clears it
$B set --workers <n> # dishes in progress or in review at once. A missing value reads as 2
$B set --reports-to <thread> # the executive admin this restaurant reports through. --reports-to "" clears it
$B set --mode light|full # this restaurant's mode from the next brief. --mode "" returns it to the roles files
$B ticket add --summary "<request>" --source user|github|<feed> [--ref <ref>] [--request A<n>] [--again] # prints T<n>. --request refuses a second ticket for that request. --again lifts the same-summary refusal in Filing tracker work step 4
$B ticket add --from-report reports/D2.md [--dry-run] # one waiting ticket per follow-up in that report, source report. Skips text already on the rail in any state. A run changes only the ticket table, the log, and `lastActivityAt`. --dry-run changes no table and no log
$B ticket list [--state waiting|assigned|moved|done|dropped]
$B ticket set T3 --state dropped
$B ticket move T3 --to <sibling> # hands a waiting ticket to a sibling. Prints the thread to tell
$B ticket take # files every ticket a sibling handed to this restaurant
$B inbox take # ticket take, then prints each request from the executive admin as A<n>: <line>
$B inbox done A<n> # records the request as acted on and deletes its file
$B fire --tickets T1,T3 --station bug-fix --summary "<outcome>" --paths src/a,src/b [--timebox 60] [--mode full --reason "<one line>"] # claims the lease, prints D<n>
$B brief D2 --fields <path> # JSON file, or --fields - for stdin. Writes briefs/D2.md
$B brief D2 --goal '...' --acceptance '...' [--acceptance ...] --verify '...' --base main [--context ...] # same brief. Single-quote every field
$B watch # a line for every open dish; renews each live lease
$B hang D1 --provider <provider> --minutes <n> [--attempt <n>] # one open run after report-back, once per attempt
$B dish D2 --state dropped [--stopped <run id>|idle] # releases the dish's lease, returns its tickets to waiting
$B dish D2 --state in-progress|in-review|queued|merged [--thread <id>] [--task <id>] [--pr <url>] [--sha <head>] [--timebox <m>] [--reported] [--lease <id>] [--paths <paths>] [--branch <name>]
$B dish D2 --mode full --reason "<one line>" # records the item's move to full once. --mode light exits 1
$B pass record D2 --sha <head> --verdict pass|send-back|blocked --author <provider/model> --verifier <provider/model> [--pr <url>] [--note "..."] [--same-family] [--report <file>] [--member | --late]
$B pass check D2 --sha <head> [--json] # exits 1 unless that SHA passed. --json prints verdict, author, verifier, note, and crossFamily
$B 86 add --question "..." --options "a, b" --default "a" [--dish D2] # prints Q<n>
$B 86 add --dish D2 --round-budget # the decision D2 owes at three send-backs. prints Q<n>
$B 86 answer Q1 --answer "..." ; $B 86 list
$B close [--dry-run | --to-file] # report of what changed since the last one. --to-file prints only the written file's path
python3 <skills>/brigade/scripts/brigade.py walk [--repo <project root>]
L="python3 <skills>/landing/scripts/land.py --repo <project root>" # leases and the landing queue. Lease, submit, share, and contest writes pass --owner <restaurant>/@<generation>. cap and mode take none
```
Pass brief fields with `--fields <path>`, or with `--fields -` and the JSON on stdin. The object has `goal`, `acceptance`, `verify`, and `base`. `acceptance` is a list of strings. Optional `context` is a list of strings. Optional `paths` and `lease` fall back to the row from fire when omitted. The flag form prints the same brief. Use the file, or put every flag value in single quotes. A double-quoted value lets the shell run backticks and expand `$` before `brigade.py` starts, so the brief can contain command output. An unquoted heredoc expands backticks and `$` the same way. Write the file with a file tool, or with a heredoc whose delimiter is single-quoted, such as `<<'EOF'`.
Each intake source has one owner. `open --intake` records the list when it creates a restaurant. On an existing one it changes nothing. `set --intake`, and `open --intake` when it creates a restaurant, refuse a source a sibling already lists, with `brigade: <sibling> already owns intake from <source>; move tickets to it instead`. `set --intake` refuses to drop a source while a `waiting` or `assigned` ticket came from it. `ticket add --source <s>` for any source but `user` needs `<s>` in this restaurant's intake and in no sibling's. Its refusal names the owner. `ticket add --ref` refuses a ref that is already live here or in a sibling, with `brigade: <ref> is already T1 (waiting); nothing added`. A `waiting`, `assigned`, or `moved` ticket is live until the ticket it was handed to is `done` or `dropped`. A ref names the work's item in the project's tracker. Use the item's URL when the tracker gives items URLs, such as the URL `gh` prints for a GitHub issue. Otherwise use the id with the project prefix the tracker prints, such as `app-7f3`. Never pass a bare number such as `123`, because another tracker can use it too. Refs compare as exact strings, so write each item in that one form. The ref stays unchanged through every `ticket move` and `ticket take`. A table line that does not parse stops every command that takes `--at` with `brigade: <table> line <n> is malformed; fix or remove it`. Fix or remove that line by hand.
Every command takes `--help`. Workers write their reports to `<restaurant dir>/reports/<dish>.md`, and verifiers write round `<k>`'s findings to `<restaurant dir>/reports/<dish>-review-<k>.md`, where `<k>` counts that dish's reviews from 1.
`open --workers` records the cap when it creates the restaurant. `set --workers` changes it. A missing `workers` field reads as 2. The cap counts dishes in progress or in review. `fire` refuses before it claims a lease when the count is already there, and `dish --state in-progress` or `in-review` refuses when that dish is not already one of those states. Moving between those states, or replacing the worker, does not. A refused `fire` leaves the tickets waiting and logs why. `watch` rechecks the worker count and then the lease, in `fire`'s order, so both name the same reason when both caps hold. `ticket list` and `walk` run the same recheck, so `ticket list` appends `blocked: workers`, `blocked: lease`, or `blocked: repository` and `walk` counts the ticket blocked only while that block still holds. `watch` prints `waiting on L<n> (<holder>)`, `waiting for room in the repository (<n> of <n> changes in flight)`, `waiting for a worker (<running> of <cap> running)`, or `unblocked` with a shell-quoted `fire` command. Run that command as printed. A refused `fire` logs the paths and `--branch` it was given. `watch` adds `changes/<branch>.md` for the branch the next `fire` would use when it rechecks the lease, and the command includes `--branch` when one was recorded. When the branch is known, `fire --paths` also leases `changes/<branch>.md`, with `%` written as `%25` and `/` written as `%2F`. A lease check that fails prints the landing queue's diagnostic. `dish --lease` and `dish --paths` record a lease claimed again, and refuse a merged or dropped dish. `dish --branch` records the branch and claims or releases nothing. The landing queue cannot add paths to a lease. When the dish's active lease does not cover the new `changes/<branch>.md`, `dish --branch` prints the three commands that do: `$L lease claim` on the old paths plus the new fragment, `$B dish --lease <new lease> --paths`, and `$L lease release` of the old lease. Run them in that order. When the lease is submitted, it prints that the submitted commit lands as it is, and to run the same `dish --branch` again if the entry bounces. When `fire` cannot release a lease it claimed and then could not keep, it warns with the lease id and the landing queue's diagnostic. `open` warns when `workers` is at or above the repository cap and a sibling exists.
Every store write checks its owner. `restaurant.json` holds the recorded `thread` and a `generation`. `set --thread` sets the generation to 1 when it records a first thread, and `--replace` adds 1. `status` prints `owner <thread>@<generation>`. Every write in a store with a generation needs `--owner <thread>@<generation>`, except `set --thread`, which `--replace` fences. The check runs under the store lock, and the lock is held through the write. A token that does not match exits 1 with `brigade: owner <token> is stale; this store is owned by <thread>@<generation>` and changes nothing. `set --thread --replace` first raises the landing floor for `<restaurant>/` with `land.py owner`, so `land.py` also refuses the replaced thread's `--owner <restaurant>/@<old generation>`. `brigade.py` passes `--owner <restaurant>/@<generation>` from its own token on every `land.py` write it makes. A store opened before generations has no `generation` and needs no `--owner`. Run `$B set --thread <its recorded thread>` once to record generation 1. From then on every write needs the token.
`dish --state dropped` releases the dish's active lease, including a queued dish whose entry bounced. On a dish that holds a lease and records a worker thread it refuses without `--stopped`, with `brigade: D3 holds L4 and its worker may still be running; wait for its run with t3_thread_wait, then pass --stopped <run id>`. The lease stays active, so no sibling claims its paths early. `--stopped` records the run id in `log.tsv` as evidence. The script cannot check it. The dish's `assigned` tickets go back to `waiting`, each with a log row naming the dropped dish, and the command prints `D3 dropped; T2 waiting again`. Fire them again, or drop them with `ticket set`.
- `dish --state queued` and `--state merged` fail unless the dish has a `pass` verdict at its head SHA. `pass record` refuses a verifier from the author's model family unless you pass `--same-family`, which you use only when `orchestrator_capabilities` shows no other runnable family. `--report <file>` stores the name of the round's findings file, and refuses a file that is missing from `<restaurant dir>/reports/` or is not a review report of that dish. `--member` records a panel member's row that does not carry the dish's verdict. It leaves the dish as it is, `pass check` ignores it, and it never counts as a send-back. `--late` records a round found unrecorded after the dish moved on. It leaves the dish as it is, and its `send-back` counts. `close` prints `brigade: warning: reports/<file> has no review row` on stderr and still exits 0, for each review report that no row names and that is newer than every row of its dish without a report. Record that round with `$B pass record <id> --report <file>`, and add `--late` when the dish has moved past it. The round budget stops another fix round. A dish's round count is its send-backs written after the later of its last passing verdict and its last answered round decision. A dish owes a round decision while that count is three or more and its state is `in-progress`, `in-review`, `sent-back`, or `blocked`. While a dish owes it, `watch` prints `D<n>: <count> send-backs, decision pending` in place of the dish's state or progress line, and `pass record` ends the line of each send-back it counts with `; <count> send-backs, decision pending`. `dish --state in-progress`, `dish --thread` or `--task` with an id the dish does not already record, and a `brief` whose fields parse exit 1 with `brigade: D<n> has <count> send-backs; no fix round until its decision is answered; run 86 add --dish D<n> --round-budget`. `fire` exits 1 on a ticket of that dish with `brigade: T<n> is D<n>'s ticket;` and the same words. The budget refuses nothing else. `$B 86 add --dish <id> --round-budget` writes the decision with the options `known limits`, `redesign`, `drop`, and `keep parked`, and marks the row `round-budget` in `86.tsv`. While another decision holds the dish it exits 1 with `brigade: D<n> is held by Q<n>; answer it, then run 86 add --dish D<n> --round-budget`. Only `known limits`, `redesign`, or `drop` closes it. No other decision's answer restarts the count. `ROUND_BUDGET` in `brigade.py` sets the number. `$B dish <id> --reported` records that this attempt's report-back arrived. A new attempt clears that mark. The attempt is new when the dish enters in progress, and when `$B dish <id> --thread <id>` records a different worker while the dish stays in progress. That command logs a new start. `$B watch` compares the report file to that start. Recording the same thread again leaves the attempt alone. When that report-back is recorded and the report file is more than 10 minutes old while the dish is still in progress, `$B watch` prints `D<n>: reported Nm ago, not in review; read the thread (thread <id>)`. The number is minutes since the report file was written, and the parentheses name the worker thread, or `task <id>` for a delegated task.
+ `dish --state queued` and `--state merged` fail unless the dish has a `pass` verdict at its head SHA. `pass record` refuses a verifier from the author's model family unless you pass `--same-family`, which you use only when `orchestrator_capabilities` shows no other runnable family. `--report <file>` stores the name of the round's findings file. It refuses a path that is none of the bare file name, `reports/<name>`, or a path whose directory resolves to `<restaurant dir>/reports/`. It also refuses a file that is missing from `<restaurant dir>/reports/`, is not a review report of that dish, is a symbolic link, is not a regular file, or resolves outside `<restaurant dir>/reports/`. `--member` records a panel member's row that does not carry the dish's verdict. It leaves the dish as it is, `pass check` ignores it, and it never counts as a send-back. `--late` records a round found unrecorded after the dish moved on. It leaves the dish as it is, and its `send-back` counts. `close` prints `brigade: warning: reports/<file> has no review row` on stderr and still exits 0, for each review report that no row names and that is newer than every row of its dish without a report. Record that round with `$B pass record <id> --report <file>`, and add `--late` when the dish has moved past it. `close` also prints `brigade: warning: reports/<file> <what it is> and no review row of <id> names it; <what to do>` on stderr, and still exits 0, for an entry named like a review report that no row of its dish names and that is a symbolic link, is not a regular file, or resolves outside `<restaurant dir>/reports/`. When `reports/` is itself a symbolic link, that line for every such entry says to replace that link with a directory and leave the link's target as it is. Otherwise, for a symbolic link it says to remove the link and leave its target as it is, and for another entry that is not a regular file it says to move it out of `reports/`. The round budget stops another fix round. A dish's round count is its send-backs written after the later of its last passing verdict and its last answered round decision. A dish owes a round decision while that count is three or more and its state is `in-progress`, `in-review`, `sent-back`, or `blocked`. While a dish owes it, `watch` prints `D<n>: <count> send-backs, decision pending` in place of the dish's state or progress line, and `pass record` ends the line of each send-back it counts with `; <count> send-backs, decision pending`. `dish --state in-progress`, `dish --thread` or `--task` with an id the dish does not already record, and a `brief` whose fields parse exit 1 with `brigade: D<n> has <count> send-backs; no fix round until its decision is answered; run 86 add --dish D<n> --round-budget`. `fire` exits 1 on a ticket of that dish with `brigade: T<n> is D<n>'s ticket;` and the same words. The budget refuses nothing else. `$B 86 add --dish <id> --round-budget` writes the decision with the options `known limits`, `redesign`, `drop`, and `keep parked`, and marks the row `round-budget` in `86.tsv`. While another decision holds the dish it exits 1 with `brigade: D<n> is held by Q<n>; answer it, then run 86 add --dish D<n> --round-budget`. Only `known limits`, `redesign`, or `drop` closes it. No other decision's answer restarts the count. `ROUND_BUDGET` in `brigade.py` sets the number. `$B dish <id> --reported` records that this attempt's report-back arrived. A new attempt clears that mark. The attempt is new when the dish enters in progress, and when `$B dish <id> --thread <id>` records a different worker while the dish stays in progress. That command logs a new start. `$B watch` compares the report file to that start. Recording the same thread again leaves the attempt alone. When that report-back is recorded and the report file is more than 10 minutes old while the dish is still in progress, `$B watch` prints `D<n>: reported Nm ago, not in review; read the thread (thread <id>)`. The number is minutes since the report file was written, and the parentheses name the worker thread, or `task <id>` for a delegated task.
Each item runs in full or light mode per [the runtime's Modes section](../pstack-runtime/SKILL.md#modes). `open --mode` and `set --mode` record this restaurant's mode in `restaurant.json`. `set --mode ""` removes it, so the project and user roles files decide. A change applies from the next brief, and a running worker keeps the mode its brief names. `brief` runs `roles.py mode` in the project root with that mode, the dish's leased paths, the station as the playbook, the attempt kind, and the count of `send-back` rows in `pass.tsv`, `--member` rows excluded. It writes the printed lines unchanged, then `Gate: brigade` and the runtime's seat rule. The attempt kind is `fix` after a send-back, `bounce` after a queue bounce, and `first` otherwise. A replaced worker keeps the kind. A station that is not a poteto-mode playbook, such as `correct`, gets only the `Mode:` and `Mode source:` lines. `roles.py` runs outside the store lock. When the store changes meanwhile, `brief` resolves again, and after three changes it exits 1 with `brigade: D<n> changed while resolving its mode; run brief again`. The first time `roles.py` prints `Mode source: escalated: <reason>`, `brief` appends one `mode` row with state `full` to `log.tsv`. Every later brief passes that reason back, so no setting returns the item to light. `fire --mode full --reason` and `dish <id> --mode full --reason` append the same row. A second call appends nothing and prints the first reason. `dish --mode full` refuses a queued, merged, or dropped dish. `--mode light` on an item exits 1.
## Open a restaurant
Run from any thread.
1. Name the target project and focus. List projects with `t3_project_list`. A restaurant lives in exactly one T3 project, because a head chef can only read and steer threads in its own project.
2. Draft the menu from evidence: the repo's README and AGENTS.md, open work in the tracker AGENTS.md names, read with the commands it names, and recent threads via the **recall** skill. Use `gh issue list` only when AGENTS.md names no other tracker. When the named tracker cannot be read, say so in the draft. Never read GitHub Issues in its place. Ask the user only for what the evidence cannot settle, normally the purpose itself. Use the **grilling** skill when the purpose is vague.
3. Ask the user three things with the host's question tool, unless they already said each one. Ask all three in one call.
- Who lands work. This decides whether the user stays a gate on PRs and git. Offer:
- `merge` (recommended for a repository with a remote): every change still gets a PR as its record, and the queue merges it once the checks pass. The user reviews what landed afterward and does no PR or git work.
- `human`: every change gets a PR, and the user merges it.
- `push`: no PRs. The queue pushes trunk after the checks pass.
- `local`: nothing leaves the machine. Changes land on a lane ref the user merges.
Say that `merge` and `push` put reviewed changes on trunk with no human gate. When the repository already has a landing contract (`$L status`), its mode applies to every restaurant on that repository. Say so before changing it, and name the siblings `walk --repo <root>` lists.
- How often the coordinator replies. Recommend `milestones`. Send the scheduled evening report at every level. The 09:00 morning service is not a report. A message from the user gets at least a one-line acknowledgment at every level. A direct question gets an answer. Each level below adds the replies it names. It does not drop the evening report, the user message, or the direct question.
- `every-turn`. Send a short reply after every wake.
- `milestones`. Reply when work merges, when a review sends work back or blocks it, when a decision needs the user, when something fails or the queue pauses, or when the user sends a message. A routine wake ends with no reply, or with a single line when the host requires text. A liveness check with nothing new, a liveness check while a review is pending, a review starting, and a worker launching are routine wakes.
- `digest`. Reply only for a decision the user must make, a failure or a paused queue the coordinator cannot fix itself, one summary when a batch drains, the scheduled evening report, and a message from the user. Every other wake ends with no reply text at all. Each reply is a few plain sentences for a person, not an engineer.
- Which mode, per [the runtime's Modes section](../pstack-runtime/SKILL.md#modes). Offer `full`, recommended when no provider is near its usage limit, and `light`, which keeps tests, the build, and a review by another model family and cuts the panels around them. When the user has no preference, record nothing, and the project and user roles files decide.
4. Run `python3 <skills>/brigade/scripts/brigade.py open --project-root <root> --name "<restaurant>" --reporting <level> [--workers <n>] [--mode light|full]`. It prints `opened <dir>` or `exists <dir>`, then one block per sibling coordinator on the same project root. Each block names that coordinator, its store directory, its thread, its purpose, and what it does not take. Fill `menu.md`. Keep this purpose clear of every sibling `open` printed. Under `## Off the menu`, list each sibling's purpose as that sibling's work. Append house rules. The list is forbidden paths, the verification bar, intake sources, `--workers`, and the repository's hot shared files, and where their changes go instead. Run `$B set --intake <sources>` with each intake source the house rules name, except a source a sibling's `restaurant.json` already lists. That sibling owns it and moves you the tickets that are yours. Hot shared files are `CHANGELOG.md`, `README.md`, and `docs/guide.md`. A change adds its one changelog bullet as a file under `changes/` and does not edit `CHANGELOG.md`. It edits `README.md` or `docs/guide.md` only when its ticket is about them, or when it removes or renames something they name. Otherwise the worker lists that doc edit under follow-ups in its report, and the coordinator batches those follow-ups into one docs item. The lease orders the batches. The default level is `milestones` when `--reporting` is omitted. Opening an existing restaurant does not change its level. Change it later with `$B set --reporting <level>`. `open` records the mode only when it creates the restaurant. Change it later with `$B set --mode`.
5. Set the repository's landing contract to the choice. When `$L status` shows no contract, run `$L init --mode <choice>` per the [landing skill](../landing/SKILL.md#set-up-a-repository-once), with the repository's own test and type-check commands as checks. When it shows another mode, run `$L mode <choice>`. It names the holders of unreleased leases. Then tell each sibling the new mode with `t3_thread_send`, to the threads `walk --repo <root>` prints. The landing contract is the only record of the mode. House rules do not restate it. A restaurant whose house rules already state the mode deletes that rule on its next service.
6. Launch the head chef only when `open` printed `opened`. That word goes to the one caller that created the directory. When `open` printed `exists`, do not launch. When the output includes `thread <id> already recorded`, that thread is the coordinator. An `exists` coordinator with no recorded thread is the user's call. Two threads never write one store. `$B set --thread` refuses to replace a recorded thread unless you pass `--replace`. When `open` printed `opened`, launch with `t3_thread_launch`: `projectId` of the target, `workspaceStrategy: {"type": "root"}`, title `Head chef: <restaurant>`, and a `message` that says "Use the brigade skill. You are the head chef for the restaurant at `<restaurant dir>`. Run your first service." Record the returned `threadId` with `$B set --thread <id>`.
7. Tell the user where the thread is and which landing mode the repository uses. If it is in another project, you cannot read or message it after launch. That is expected.
Opening a restaurant is the user's request for top-level threads: the head chef, and one worktree thread per worker.
## First service
1. `t3_thread_organize` with `action: "pin"` and no `threadId`.
2. Call `orchestrator_capabilities` and resolve roles per [the runtime's Roles section](../pstack-runtime/SKILL.md#roles). The roles files hold the budget. Seats are resolved per item in Run a service steps 4 and 6.
3. Create three schedules with `schedule_task`, bound to this thread, each with a self-contained prompt: "Use the brigade skill. You are the head chef for the restaurant at `<restaurant dir>`. Reporting level: `<level>`. Follow Run a service step 9. Run a service." Read `<level>` from `restaurant.json` (`every-turn`, `milestones`, or `digest`). A missing `reporting` field is `milestones`. Add the purpose of the run to each. A later `$B set --reporting` changes the level, and the next service reads `restaurant.json` rather than the level copied into an older prompt.
- Morning service: `{"type": "fixed_time", "timeOfDay": "09:00"}`. The prompt carries the reporting level.
- Intake: an interval matched to the sources in the house rules, at least `3600000`. Skip it when the menu names no source. A restaurant whose `restaurant.json` has `reportsTo` keeps this schedule even with no source, at `3600000`, because each service starts with `inbox take`. The prompt carries the reporting level.
- Evening report: `{"type": "fixed_time", "timeOfDay": "18:00"}`, prompt adds "Write the report." The prompt carries the reporting level.
- While this restaurant has dishes queued, keep a landing drain schedule per the [landing skill](../landing/SKILL.md#keep-the-queue-moving), and delete it when none are. Name the reporting level in that prompt, and say to follow Run a service step 9. After the queue opens a PR, that section has this thread call `watch_pull_request` and run `land` on each wake. Other restaurants' drains on the same repository are harmless. The queue lock runs one at a time. When you delete that schedule, run `$B set --schedule drain=`.
- While `$B watch` prints anything other than "no work in progress", and on the first refused `fire`, keep a liveness schedule. `watch` renews every live lease, so this schedule is what keeps a lease from expiring. At `every-turn` and `milestones` use `{"type": "interval", "everyMs": 600000}`, every 10 minutes. At `digest` use `{"type": "interval", "everyMs": 1800000}`, every 30 minutes, because worker report-backs and pull request watches are the primary wakes. The prompt is "Use the brigade skill. You are the head chef for the restaurant at `<restaurant dir>`. Reporting level: `<level>`. Follow Run a service step 9. Run the liveness check." Delete it when `$B watch` prints "no work in progress". When you delete that schedule, run `$B set --schedule liveness=`. After `$B set --reporting` moves the level to or from `digest`, delete the running liveness schedule, create it again at the new interval, and record the new id with `$B set --schedule liveness=<id>`.
- Record each ID with `$B set --schedule <name>=<id>`. When the landing drain schedule is recreated, record the new id with `$B set --schedule drain=<id>`. Report schedules only in a reply Run a service step 9 already allows. At `digest`, creating, recreating, or deleting a schedule is not a reply occasion. The IDs stay in `restaurant.json`, and `list_scheduled_tasks` shows each cadence and `nextRunAt`. A first service started by opening the restaurant, and a reply to a user's level change, may name the schedules within Digest messages. This overrides the runtime's "Report the returned cadence and `nextRunAt`" for a head chef at `digest`.
4. Rerun `python3 <skills>/brigade/scripts/brigade.py open` with the same project root, name, and reporting level. Two coordinators opened at the same moment both see template purposes, so this step runs once those menus can be filled. Compare each sibling purpose it prints with this coordinator's `menu.md`. Keep this purpose clear of those siblings. Add any missing sibling purpose under `## Off the menu` as that sibling's work. A sibling that prints `purpose: not written yet` has not filled its menu. Leave that sibling out of this menu until a later `open` prints a purpose.
5. Run a service.
## Filing tracker work
Coordinators and the executive admin follow this section before each `ticket add` for a user request or a supplier finding. The tracker is the one AGENTS.md names. Read its open items with the commands AGENTS.md names. Run `gh issue list` only when AGENTS.md names no other tracker. The rail records the work a restaurant runs. It is not the project's tracker.
1. **Match.** A ref the request names is its ref. Otherwise read the open items and find the one item that is the same work as the request, by its summary. Pass that item's ref as `--ref` and create nothing. When more than one item could be the same work, create nothing and add the ticket with no `--ref`. A scope that forbids tracker writes still passes an existing item's ref.
2. **File.** When no item matches, follow AGENTS.md's filing rules. Create an item only when those rules call for one and the user or the house rules allow that write. Pass the ref the tracker prints for it. When the rules or the permission do not allow the write, create nothing and add the ticket with no `--ref`.
3. **Unavailable.** When the tracker cannot be read, report it and fetch nothing in its place. A user request is still filed. Pass the ref it names as `--ref`, and omit `--ref` only when it names none. A supplier finding with no ref adds no ticket.
4. **Refusals.** A refused `ticket add` is not an error to work around. Four of its refusals are about the request. A live ticket here or in a sibling has the ref. A ticket of this restaurant already carries the `--request` id. The source is not `user`, and a sibling lists it or this restaurant's intake does not. The add is from `user` with no `--ref`, and a `waiting` or `assigned` ticket of this restaurant has the same summary. Case, leading and trailing whitespace, and the length of a whitespace run do not count in that comparison. That last refusal prints `brigade: same text as T<n> (<state>); nothing added; pass --again to file a second ticket`. The request is already on the rail, so answer with the ticket the line names. Pass `--again` only when the request is separate work in the same words. `--again` lifts no other refusal. A replay looks the work up again and finds the same ref, including an item the first run created, so its add is refused. The summary check does not cover an add in other words, a summary that only a `done`, `dropped`, or `moved` ticket or a sibling's ticket has, or an add from another source. So read `$B ticket list` before you file a no-ref request again.
## Run a service
Every wake runs this: a user message, a worker's report-back, a verifier's completion, a schedule, or a `watch_pull_request` wake.
1. **Read.** `menu.md`, `house-rules.md`, `$B status`, `$B 86 list`. Re-read the menu every service. It is the purpose every decision answers to. `$B status` prints `thread <id>` first and `owner <thread>@<generation>` last. When it names another thread, end the turn with no further command. Otherwise set `$B` to `brigade.py --at <restaurant dir> --owner <thread>@<generation>`, and pass `--owner <restaurant>/@<generation>` on every `$L lease claim`, `lease renew`, `lease release`, and `submit`. When `status` prints no owner line and `restaurant.json` records this thread, run `$B set --thread <this thread>` once and read `status` again. A write refused with `is stale` means another thread replaced this one. End the service with no further command.
2. **Take tickets.** Start with `$B inbox take`. It files each ticket a sibling or the executive admin handed to this restaurant, then prints each request from the admin as `A<n>: <line>`. Act on each request per [Reporting to an executive admin](#reporting-to-an-executive-admin), then run `$B inbox done A<n>`. Each user request or supplier finding becomes `$B ticket add`, with `--source user` or the intake name of the source it came from, such as the tracker AGENTS.md names, and the `--ref` that [Filing tracker work](#filing-tracker-work) gives it. Fetch only the sources in this restaurant's intake. A ticket that is a sibling's work goes to it with `$B ticket move <id> --to <sibling>`. Then call `t3_thread_send` to the thread it prints, with mode `"auto"`, the line `ticket <sibling>: run ticket take`, and the handoff id as `clientRequestId`. The handoff id is this restaurant's store path, the last two parts of `<restaurant dir>`, then the ticket, such as `app/engine/T6`. Fetch supplier sources only when the house rules name them and this restaurant owns them. Read backlog from the tracker that [Filing tracker work](#filing-tracker-work) names. Read pull requests with `gh pr list` only when the house rules name pull requests and this restaurant owns that source. Read notifications only when the house rules name them and this restaurant owns that source. Drop a ticket that is off the menu with `$B ticket set <id> --state dropped` and say why in the next report. At `digest` a dropped ticket is not a reply occasion.
3. **Group before firing.** Read the waiting tickets together. Several reports of one cause are one dish. Fire a ticket alone only when it is urgent or unrelated to the rest. A ticket that is a whole program with a done predicate runs as one dish whose station is poteto-mode's Orchestrate playbook.
4. **Fire.** Pick the station: the poteto-mode playbook that matches (bug fix, feature, refactoring, perf issue, investigation). Then:
1. `$B fire --tickets ... --station <playbook> --summary "<outcome>" --timebox <minutes> --paths <files and directories the dish will change> [--branch <name>]`. Pass `--mode full --reason "<one line>"` when the tickets name a lock, a lease, a race, a migration, or history rewriting. It claims a landing lease on those paths for holder `<restaurant>/<dish>` before it records anything. `fire` refuses when this restaurant's running workers are already at `--workers`. A refused claim names the holder and fires nothing. A refused `fire` leaves the tickets waiting. On the first refusal, create the liveness schedule from First service step 3 if `restaurant.json` has no `liveness` id. Fold the tickets into the holder's dish when it is this restaurant's, or leave them waiting until that lease is released. Never pass `.` for a dish that touches a few files. Size the timebox to the work, 30 to 90 minutes. The timebox orders the work. It never waives a playbook step. How, Architect, investigation, and the implementation delegate stay in the work. At the limit, the worker writes the report with what remains.
2. `$B brief <dish> --fields <path>`. The file is the JSON object described after the command block. `--fields -` reads it from stdin. The flag form single-quotes every value. Use the file or those single quotes. A double-quoted value lets the shell run backticks and expand `$` before `brigade.py` starts. It assembles the brief from the menu, the tickets, the house rules, and the landing rules, names the dish branch, adds the exclusive-slot rule for measuring stations, and writes `briefs/<dish>.md`. The brief carries the item's `Mode:` lines and `Gate: brigade`. Read its `Mode:` value for steps 4.3 and 6. The report section first tells the worker never to end its turn while a child task it delegated is open, per [the runtime's Delegation section](../pstack-runtime/SKILL.md#delegation). It gives a thread the worker launched the same bounded wait, except a long-lived owner its playbook supervises, which follows [the runtime's Top-level threads section](../pstack-runtime/SKILL.md#top-level-threads). It then tells the worker to call `t3_thread_send` on the coordinator thread after the report file is written. It refuses when a field is missing, and when no coordinator thread is recorded. Run `$B set --thread` first. Fix the field. Never hand-write a brief.
3. Launch the worker with `t3_thread_launch`: title `<restaurant> <dish>: <summary>`, `workspaceStrategy: {"type": "worktree", "baseRef": "<trunk>", "branch": "<dish branch from the brief>", "startFromOrigin": true}` (local landing mode: `baseRef` `refs/landing/<trunk>` and `startFromOrigin` false), and the brief file's contents as `message`. Resolve the station's seat with `roles.py show --brief-mode <the Mode: value in briefs/<dish>.md>` and no other mode flag, per [the runtime's Roles section](../pstack-runtime/SKILL.md#roles), so an item moved to full gets full seats. Build `modelSelection` from that seat per [the runtime's Top-level threads section](../pstack-runtime/SKILL.md#top-level-threads). Omit `modelSelection` for an `inherit` seat. Confirm the applied options per [the runtime's Delegation section](../pstack-runtime/SKILL.md#delegation) before step 4.4. Step 4.4 records the thread only after the readback matches. On a mismatch, stop the thread per the runtime, record nothing, and park the dish with `$B 86 add --dish <id> --question "<id>: <this thread's provider> could not pass the seat's options: <the tool error>. Move this coordinator to a provider that passes them?" --options "moved, keep parked" --default "keep parked"`. The item is then held. `$B 86 list` prints `Q<n> for <id>:`. `$B watch` prints `<id>: open decision Q<n>: ...` in place of its other lines. A second `$B 86 add --dish <id>` prints the same id and appends nothing, so never raise it again. Record every answer with `$B 86 answer Q<n> --answer "<answer>"`. Only `moved` closes the decision, matched without regard to case or a trailing period, and the command prints `Q<n> answered`. Any other answer, `keep parked` included, writes nothing and prints `Q<n> still open`, so the item stays held. Only the user can move the coordinator to a provider that passes options. Never relaunch the worker without the options.
4. `$B dish <dish> --thread <threadId>` only after the readback in step 4.3 matches.
5. **End the turn** while dishes run. A worker's report-back message wakes the coordinator, which marks the dish reported with `$B dish <id> --reported` and reviews it per step 6. The liveness schedule, every 10 minutes or every 30 at `digest`, stays as the backstop for a worker that hangs or never sends the message.
6. **Review.** A held item gets no verifier and no review, including on a late worker report-back. When a worker is done, read its report. When it says `Contested: <reason>` under its status line, follow **Contested** below instead of the rest of this step. Otherwise read its diff yourself. A worker's "done" is a claim. The timebox orders the work. It never waives a playbook step. How, Architect, investigation, and the implementation delegate stay in the work. At the limit, the worker reports what remains. The coordinator checks that the report names what remains. An unexplained skip in the report's deviations is a send-back. A skip cited to the timebox is a send-back. A step the brief's `Waived by mode:` line names is not a skip. `t3_thread_read` on the worker thread returns its `worktreePath` and branch. Run `$B dish <id> --state in-review --sha <head>`. Spawn one verifier with `delegate_task`, `mode: "async"`, from the `verifiers` role, resolved with `roles.py show --brief-mode <the item's Mode: value>`, on a model family other than the author's. Its `target` is that seat unchanged. Right after the spawn, confirm its options with `t3_thread_configuration` on the returned `childThreadId` before the verdict counts, per [the runtime's Delegation section](../pstack-runtime/SKILL.md#delegation). On a mismatch, call `task_cancel`, record no verdict, and park the dish and record the user's answer the same way step 4.3 does. Its read-only brief: the tickets, the menu, the worktree path and the diff at that SHA, two questions (does it work on the real surface, and does it serve the menu without scope the tickets did not ask for), and "write your findings to `<restaurant dir>/reports/<dish>-review-<k>.md`". When the brief says `Mode: light`, write the verifier's brief per [the runtime's Gate review](../pstack-runtime/SKILL.md#gate-review) step 2, with the comment rules, the gate contract, the chosen design, and the decision trail when they apply. Then add this step's tickets, menu, and two questions below the contract. The contract forbids file edits, so write the report the verifier returns to `<restaurant dir>/reports/<dish>-review-<k>.md` yourself. Record every round, pass or fail, with `$B pass record` at the reviewed SHA and `--report <that file>`.
**Contested.** A worker whose brief says `Mode: light` stops at a verifiable point when it finds its design contested, and reports `Contested: <reason>`, per [the runtime's Modes section](../pstack-runtime/SKILL.md#modes). Do not mark the dish in review, and spawn no verifier. Call `t3_thread_interrupt` on the old worker, then `t3_thread_wait` on the run id it returned. When the wait returns `timedOut: true`, leave everything as it is and wait again on the next service. After a terminal state, finish with the old worker per [Git and PR housekeeping](#git-and-pr-housekeeping). Run `$B dish <id> --mode full --reason "contested: <reason>"`. Create a new branch from the old branch's head and record it with `$B dish <id> --branch <new branch>`. Rerun `$B brief` with `--base <the old branch>` and `--context` naming the report path. When the item is held, launch no replacement worker. Otherwise launch a fresh worker per steps 4.3 and 4.4 on the new branch and the full seat its brief names. The dish stays in progress and keeps its lease. Never release or claim the lease again for this replacement, so skip the lease commands `dish --branch` prints. The fresh worker writes its changelog bullet to the `changes/` file its brief's leased paths name. A contested report is not a send-back. It records no `pass.tsv` row and does not count toward the second send-back.
7. **Act on the verdict.** A held item gets no send-back worker and no bounce worker.
- - `pass`: first run `$B ticket add --from-report reports/<dish>.md`. It files the follow-ups in the worker's report as waiting tickets and skips text already on the rail. It prints a `not filed` line for a paragraph that introduces a list and for a paragraph after a section's last list item. It also prints one for a block whose whole text is `None`, `None required`, `None needed`, `Nothing`, `Nothing needed`, `N/A`, `Not applicable`, or `No follow-ups`, in any letter case, with or without list markers, a final period, and `*`, `_`, or backtick marks around it. Read each `not filed` line, and file a real follow-up among them with `$B ticket add --summary`. Drop a ticket that is not worth an item with `$B ticket set <id> --state dropped`, or fire it with related tickets as one dish per step 3. When the dish depends on another coordinator's item that has not landed and `$B status` prints `reports to <thread>`, never hold it outside the queue. First open a contest per [Contests](#reporting-to-an-executive-admin) and send the `contest` line. The contest holds this dish's entry until the admin orders the two. Then submit as below. Write the PR title and body (what changed, the measured effect, how it was verified) to `<restaurant dir>/prs/<dish>.md`, then `$L submit --holder <restaurant>/<dish> --branch <b> --sha <head> --lease L<n> --reviewer <provider/model> --title "..." --body-file <restaurant dir>/prs/<dish>.md`, then `$B dish <id> --state queued`, then `$L land`. When it lands, `$B dish <id> --state merged`. In `merge` and `human` mode it opens a PR first. Record it with `$B dish <id> --pr <url>`, link it with `link_pull_request`, and call `watch_pull_request` on it per [Pull request watching](../pstack-runtime/SKILL.md#pull-request-watching). This head chef thread owns the PR. On each wake, run `$L land`. Keep the watch while the dish is queued or awaiting merge. A reply, or a wake with no reply, keeps the watch. Call `unwatch_pull_request` only when this thread stops driving that PR. Driving stops when the dish is dropped, when its PR closes, when the queue bounces it and leaves the PR open, or when the restaurant closes. If this thread is settled, call `t3_thread_organize` with `action: "unsettle"` before the next `watch_pull_request`. This thread is pinned, so a merge does not auto-settle it. Mark the dish merged when a later `land` reports it landed.
+ - `pass`: first run `$B ticket add --from-report reports/<dish>.md`. It files the follow-ups in the worker's report as waiting tickets and skips text already on the rail. It prints a `not filed` line for a paragraph that introduces a list and for a paragraph after a section's last list item. It prints one for text that follows the word on a follow-ups heading line. It prints one for a heading deeper than the follow-ups heading that does not itself start with follow-ups, with the heading's text when it has any. It also prints one for a block whose whole text is `None`, `None required`, `None needed`, `Nothing`, `Nothing needed`, `N/A`, `Not applicable`, or `No follow-ups`, in any letter case, with or without list markers, a final period, and `*`, `_`, or backtick marks around it. Read each `not filed` line, and file a real follow-up among them with `$B ticket add --summary`. Drop a ticket that is not worth an item with `$B ticket set <id> --state dropped`, or fire it with related tickets as one dish per step 3. When the dish depends on another coordinator's item that has not landed and `$B status` prints `reports to <thread>`, never hold it outside the queue. First open a contest per [Contests](#reporting-to-an-executive-admin) and send the `contest` line. The contest holds this dish's entry until the admin orders the two. Then submit as below. Write the PR title and body (what changed, the measured effect, how it was verified) to `<restaurant dir>/prs/<dish>.md`, then `$L submit --holder <restaurant>/<dish> --branch <b> --sha <head> --lease L<n> --reviewer <provider/model> --title "..." --body-file <restaurant dir>/prs/<dish>.md`, then `$B dish <id> --state queued`, then `$L land`. When it lands, `$B dish <id> --state merged`. In `merge` and `human` mode it opens a PR first. Record it with `$B dish <id> --pr <url>`, link it with `link_pull_request`, and call `watch_pull_request` on it per [Pull request watching](../pstack-runtime/SKILL.md#pull-request-watching). This head chef thread owns the PR. On each wake, run `$L land`. Keep the watch while the dish is queued or awaiting merge. A reply, or a wake with no reply, keeps the watch. Call `unwatch_pull_request` only when this thread stops driving that PR. Driving stops when the dish is dropped, when its PR closes, when the queue bounces it and leaves the PR open, or when the restaurant closes. If this thread is settled, call `t3_thread_organize` with `action: "unsettle"` before the next `watch_pull_request`. This thread is pinned, so a merge does not auto-settle it. Mark the dish merged when a later `land` reports it landed.
- Bounced by the queue: the lease is active again. Call `unwatch_pull_request` on the PR the bounce left open. Finish with the old worker per [Git and PR housekeeping](#git-and-pr-housekeeping). Then run `$B dish <id> --state in-progress`, because `brief` refuses a queued dish. That command clears the recorded worker thread. Rerun `$B brief` with `--context` naming the bounce reason and current trunk. Launch a fresh worker per steps 4.3 and 4.4, unless the item is held. A conflict or a changed rebase needs a new review.
- When `pass record` or `watch` prints `<n> send-backs, decision pending`, the dish owes its round decision per [The script](#the-script). Follow this bullet in place of the `send-back` bullet below. Stop patching and launch no fix worker. Finish with the old worker per [Git and PR housekeeping](#git-and-pr-housekeeping) when the dish is sent back. Run `$B 86 add --dish <id> --round-budget` and raise that one decision in the reply. Record the user's choice with `$B 86 answer Q<n> --answer "<option>"`. On `known limits`, run step 6 again at the same head with the accepted findings listed in the verifier's brief as known limits, then land the dish on its pass. On `redesign`, follow the `send-back` bullet from `$B dish <id> --state in-progress`, and give `$B brief` a `--context` that tells the worker to run the architect skill before code and names every round's findings file. On `drop`, follow the drop steps below.
- `send-back`: finish with the old worker per [Git and PR housekeeping](#git-and-pr-housekeeping). Then `$B dish <id> --state in-progress`, which clears the recorded worker thread. Rerun `$B brief` with `--base <the old branch head>`. It adds the verifier's findings file and the `Attempt: fix` lines, per [the runtime's Modes section](../pstack-runtime/SKILL.md#modes). Every send-back launches a fresh worker per steps 4.3 and 4.4, on a new branch from the old branch's head, however small the fix, unless the item is held. Never call `t3_thread_send` on the old worker.
- Drop a dish in three steps. A requested interrupt is not a stop. `t3_thread_interrupt` can return `status: "interrupt_requested"` while the worker's run goes on.
1. Call `t3_thread_interrupt` on its worker thread.
2. Call `t3_thread_wait` on the run id it returned, or on the thread when it returned none. When the wait returns `timedOut: true`, leave the dish in its state. `watch` keeps its lease renewing. Wait again on the next service.
3. When the wait reports a terminal state, run `$B dish <id> --state dropped --stopped <run id>`, or `--stopped idle` when the wait reported an idle thread. That releases its lease.
- After a dish merges or is dropped, clean up per [Git and PR housekeeping](#git-and-pr-housekeeping). A send-back or a bounce already finished with the old worker above. After a send-back, delete the old branch per that section once the fresh worker is launched.
- `blocked`: `86 add` if only the user can unblock it. Otherwise fix the environment and run the pass again.
- Usage limit: when a provider in the `verifiers` role or a station's role hits its usage limit, relaunch first per [Failure handling](../pstack-runtime/SKILL.md#failure-handling). For a worker, call `t3_thread_interrupt`, then `t3_thread_wait` on the run id it returned. When the wait returns `timedOut: true`, leave the dish and wait again on the next service. After a terminal state, run `roles.py backup` with the station's role. When it prints `relaunch`, create a new branch from the old branch's head and record it with `$B dish <id> --branch <new branch>`. Rerun `$B brief` with `--base <the old branch>` and `--context` naming the report line. Launch a fresh worker per steps 4.3 and 4.4 with the printed seat in place of the station's seat. Never call `t3_thread_send` on the old worker. The dish keeps its lease, so skip the lease commands `dish --branch` prints. A held dish gets no relaunch. For a verifier, run `roles.py backup` with `--role verifiers` and repeat `--author` for every model that wrote the diff, per [Gate review](../pstack-runtime/SKILL.md#gate-review). Read them from the worker's report: each code delegate's `providerInstanceId/model`, the worker's model when the worker wrote code, and every author an earlier attempt on this branch recorded with `$B pass record`. Never pass only the worker's model when a delegate wrote the code. When every backup family is an author family, the command parks. When it prints `relaunch`, spawn a fresh verifier per step 6 on the printed seat. When it prints `panel`, spawn one fresh verifier per step 6 on each printed seat, keep the dish in review until every one is terminal, then apply the printed rule per [Failure handling](../pstack-runtime/SKILL.md#failure-handling). Member `<n>` of round `<k>` writes its findings to `<restaurant dir>/reports/<dish>-review-<k>-panel-<n>.md`. Run `$B pass record` once for each member that returned a verdict, with that member's seat as `--verifier` and its file as `--report`. When the rule passes, record each passing member's `pass` with no other flag and every other member with `--member`. When a member reproduces a blocker, record that member's `send-back` with no other flag and every other member with `--member`, and handle the send-back above. When fewer than two pass and none reproduces a blocker, record every member with `--member`, and the dish stays in review as on `park`. A relaunched or resumed worker writes its changelog bullet to the `changes/` file its brief's leased paths name. A new branch never widens the lease. On `park`, launch nothing. A parked worker keeps its lease and its branch and stays in progress. A parked verifier stays in review. Name the reset time from the error in the next report. On the first service after that time, run `roles.py backup` with `--resume`, the seat the item was approved to run on (the station's seat or the `verifiers` seat), the same `--author` flags, and `--out` for each provider whose reset has not passed. When `--resume` prints `relaunch` for the station's role, launch a fresh worker the same way as a relaunch above: a new branch from the old branch's head, `$B dish <id> --branch`, `$B brief --base <the old branch>`, and steps 4.3 and 4.4 on the printed seat. The branch's work carries over and the dish keeps its lease. When `--resume` prints `relaunch` for `verifiers`, spawn a fresh verifier per step 6 on the printed seat. When `--resume` prints `panel`, run the panel the same way. When it prints `park`, wait for the next reset time. Put the printed `report` line in the next report. Do not change the mode on your own. Park the choice with `$B 86 add --question "<provider> hit its usage limit. Run new work in light mode until it resets?" --options "light, full" --default "light"`. Run `$B set --mode light` only when the user answers light, or when the menu's `## Budget` says to switch, per [the runtime's Modes section](../pstack-runtime/SKILL.md#modes).
8. **Fix the recipe.** When two dishes repeat the same mistake, fire a dish that runs the **correct** skill to make it impossible (lint, type, test, or skill). Run the **reflect** skill over this restaurant's threads once a week. Skip it while `roles.py mode --cwd <project root>`, with `--coordinator-mode` set to the `restaurant.json` mode when one is recorded, prints `Mode: light`, per [the runtime's Modes section](../pstack-runtime/SKILL.md#modes).
9. **Report.** When `$B status` prints `reports to <thread>`, this restaurant reports through the executive admin, and this paragraph replaces the rest of this step, levels included. Send each event line per [Reporting to an executive admin](#reporting-to-an-executive-admin). A send-back, a merge, or a failure is an event line to the admin and not a reply. For the scheduled evening report, run `$B close --to-file` and send the `report` line with that path. When the batch drains, as defined below, run `$B close --to-file` and send the `drained` line. Neither is a reply to the user. Reply to the user only in a service the user started, or when a send to the admin fails. Every other wake ends the turn with no assistant text at all, whatever this restaurant's level. That covers a schedule, a liveness check, a worker's report-back, a verifier's completion, a pull-request wake, and a request from the admin. The `milestones` allowance of a single line when the host requires text does not apply. T3 requires no closing text, and a one-line status is still a reply the user reads. A failed send of a line with a store row gets a reply at this restaurant's own level, per the rest of this step. A failed send of a message-only line gets a reply at every level, because nothing else carries it. Without a `reports to` line, read `reporting` from `restaurant.json`. A missing field means `milestones`. Send the scheduled evening report at every level. The 09:00 morning service is not a report. A batch has drained when `$B status` omits `in progress`, `in review`, `passed review`, and `waiting to land`. Status omits a count of zero, so a missing label is a count of zero. `waiting to land` is work queued to land, including a pull request that awaits merge. Send the drain summary on the wake that first finds the batch drained. A later wake that still sees the batch drained does not send that summary again. A service the user started is a turn begun by a user message. A worker report, a verifier completion, a schedule, or a pull-request wake is not a service the user started. A message from the user gets at least a one-line acknowledgment at every level. A direct question gets an answer. Each level below adds the replies it names. It does not drop the evening report, the user message, or the direct question.
- `every-turn`. Send a short reply after every wake. Run `$B close` on the scheduled evening report, a reply that raises a decision for the user, the end of a service the user started, and closing the restaurant.
- `milestones`. Reply when work merges, when a review sends work back or blocks it, when a decision needs the user, when something fails or the queue pauses, or when the user sends a message. A routine wake ends with no reply, or with a single line when the host requires text. A liveness check with nothing new, a liveness check while a review is pending, a review starting, and a worker launching are routine wakes. Run `$B close` on the scheduled evening report, a reply that raises a decision for the user, the end of a service the user started, and closing the restaurant.
- `digest`. Reply only on these five wakes:
1. A decision the user must make. A blocked verdict that needs the user is a decision.
2. A failure, or a paused queue, that this thread cannot fix itself. A send-back is not a failure. A queue bounce this thread hands to a fresh worker is not a failure.
3. One summary when a batch drains.
4. The scheduled evening report.
5. A message from the user.
Every other wake ends the turn with no reply text at all. Those wakes include a worker's report-back, a verifier's completion, a send-back, a queue bounce, a worker launching or being replaced, a timebox raised, a review starting, a merge that does not drain the batch, a `watch_pull_request` wake, a landing drain run that neither drains the batch nor pauses the queue, a liveness check, intake with nothing new or only dropped tickets, a fix dish fired by step 8 or the liveness check, logging a hang for a run left open after its report-back, and creating, recreating, or deleting a schedule. A status line is still a reply. Do not send one on those wakes, even a single line. Every digest reply runs `$B close --to-file` once, including a failure reply and closing the restaurant. Write each reply per Digest messages below.
Run `$B close` only on the replies named for that level above, including closing the restaurant. At `digest` that command is `$B close --to-file`. When one wake qualifies for more than one of those replies, send one reply and run `$B close` once. Close runs once per reply. On a reply that runs `$B close`, answer a direct question first. That answer sits outside the three-sentence cap. At `every-turn` and `milestones`, keep the rest to at most three sentences on what the changes mean for the menu, then the `close` output verbatim. It lists each ticket and dish once, under its latest state since the last report, with PR links. Write no other report file. At those two levels every other reply is plain and does not run `$B close`.
### Digest messages
At `digest`, write every reply for a person who has not followed the work. Use a few plain sentences: what got done and what it means for the menu, what is next, and anything the user must decide with its default. Describe each change by what it does for the user, not by how it was built.
- Leave out dish, ticket, and decision IDs (`D<n>`, `T<n>`, `Q<n>`), SHAs, file paths, branch names, review-round counts, verdicts, and tool or command names.
- Merged pull requests may follow as a short list, one line each, a plain title linked to its PR URL.
- `$B close --to-file` writes the full report under `<restaurant dir>/closeouts/` and prints its path. End the message with one line that names that path, such as "Full report: <path>". That line is the only path in the message. Never paste the `close` output.
- Name the mode only in the first reply after `set --mode` changes it.
- Put commits, IDs, and other engineering detail in the store, not the message. A paused queue's two commits, which the [landing skill](../landing/SKILL.md#when-the-queue-pauses) asks for, go in the `$B 86 add --question` text, and the full report carries them.
A drain summary reads like this:
> Startup is about twice as fast, and the settings page no longer freezes when it opens. Both changes are merged. Next I start on the slow search results you reported this morning. Nothing needs your decision.
>
> - [Faster app startup](https://github.com/acme/app/pull/41)
> - [Fix the settings page freeze](https://github.com/acme/app/pull/42)
>
> Full report: ~/.local/state/pstack-t3/brigade/app/perf/closeouts/2026-10-06T230012.481207.md
## Git and PR housekeeping
The head chef owns every git and PR chore its work creates. In `merge` and `push` mode the user has no step. In `human` mode the user merges each PR. In `local` mode the user merges `refs/landing/<trunk>` into a branch.
- Write each PR's title and body through `submit`, and link every PR with `link_pull_request`.
- Keep the landing drain schedule while anything is queued or awaiting merge, per the [landing skill](../landing/SKILL.md#keep-the-queue-moving). After the queue opens a PR, also watch it as that section says. Keep that watch across reports while the entry is queued or awaiting merge. When you delete the schedule, run `$B set --schedule drain=`. Call `unwatch_pull_request` only when this thread stops driving that PR.
- Finish with the old worker when a dish merges, is dropped, is sent back, bounces, or is replaced after a contested report. The old worker is the thread the dish row names before any `$B dish <id> --thread` records a replacement. Name that thread id in every call below, so the replacement's open run is never touched. Read it with `t3_thread_read`. If its run is still open after the report-back, call `t3_thread_interrupt`, then `$B hang <id> --provider <provider> --minutes <n>` with the provider and the minutes that read showed, then archive it with `t3_thread_organize`, `action: "archive"`, and that thread id. A worker that an earlier attempt launched counts too. Check each attempt's thread, and for a run a replaced attempt left open, add `--attempt <n>`, counting attempts from 1 in launch order. If the run has already closed, only archive it. On a send-back or a bounce, finish before `$B dish <id> --state in-progress`. Entering in progress starts a new attempt, and `hang` then refuses with `brigade: no report-back on this attempt`. At `digest` reading, interrupting, logging a hang, and archiving send no reply of their own. A merge that drains the batch still sends the drain summary.
- After a dish merges, is dropped, is sent back, or is replaced after a contested report, remove the old worker's worktree, delete its branch locally and on the remote, and delete the queue's `landing/e<n>` branch, including a leftover `landing/q<n>`, once its PR merged or closed. After a send-back or a contested replacement, delete the old branch only once the fresh worker is launched, because its branch starts from the old head.
- After a landing, when the user's checkout at the project root is clean and on trunk, fast-forward it with `git merge --ff-only`. Otherwise leave it alone.
- A bounce or a conflict is a dish for a fresh worker, never a manual rebase.
- Never force-push trunk, rewrite published history, change branch protection, or delete a branch this restaurant did not create.
## Liveness check
Run on the liveness schedule, and at the start of any service while work is in progress.
1. `$B watch`. It prints one line per dish in progress, one line for each other dish not `merged` or `dropped`, one line per ticket moved to a sibling that has not filed it yet, and `handed to you: <n>; run ticket take` while siblings' handoffs wait in this restaurant's inbox. On `handed to you`, run `$B ticket take`. On `not delivered`, run the `ticket move` command the line prints, then message the sibling per Run a service step 2. `waiting for ticket take` needs nothing until the sibling's next service. For a waiting ticket whose latest log row is `blocked`, it prints `waiting for a worker (<running> of <cap> running)`, `waiting on L<n> (<holder>)`, or `waiting for room in the repository (<n> of <n> changes in flight)`, the first that holds now. An `unblocked` line names a shell-quoted `fire` command. Run it as printed. That is the `fire` in steps 3 and 4 of Run a service for those tickets. When `$B status` prints `reports to <thread>` and a ticket's first `blocked` row in `log.tsv` is more than an hour old, send the `blocked` line per [Reporting to an executive admin](#reporting-to-an-executive-admin).
`watch` renews the lease of every dish in progress, in review, passed, sent back, or parked, and prints nothing for a live one. It updates `lastActivityAt` after a pass that renewed every live lease, so `walk` marks a restaurant whose leases stopped renewing. A submitted lease is the queue's. Act on these lines.
- `D3: lease L4 expired; stop its worker, then run lease renew L4`. Call `t3_thread_interrupt` on the worker thread, then `t3_thread_wait` on the run id it returned. When the wait returns `timedOut: true`, leave the lease expired and the dish as it is, run no `lease renew`, and wait again on the next service. Only after the wait reports a terminal state, run `$L lease renew L4 --owner <restaurant>/@<generation>`. It admits the lease again, or refuses with the overlap or cap message. On a refusal, park the dish with `86 add` when only the user can unblock it, and run the renew again on a later service.
- `D3: lease L4 was released; claim again before submitting`. Claim the paths with `$L lease claim` and record it with `$B dish D3 --lease <id> --paths <paths>` before any submit.
- `D3: could not renew L4: <error>`. The landing store did not answer. Fix that cause. `lastActivityAt` does not move until a pass renews every live lease.
- `D2: in review`, `D3: passed, not submitted`, `D4: parked`, and `D5: sent back` keep the liveness schedule alive. Act on them per Run a service steps 6 and 7.
- `D1: 3 send-backs, decision pending`. Launch no fix worker. Follow the decision-pending bullet in Run a service step 7.
- `D1: open decision Q1: <question>; launch no worker or verifier until 86 answer Q1`. Launch nothing and spawn nothing. Read no worker thread and send it no nudge. Skip Liveness steps 2 to 5 for that item. Act on a lease line printed under it. After `$B 86 answer` prints `Q<n> answered`, the ordinary line returns.
- `D1: in progress with no worker thread; launch a fresh worker`. Launch a fresh worker per steps 4.3 and 4.4. Never call `t3_thread_send` on an earlier attempt's thread.
- For a passed or queued dish, `watch` reads `land.py status --holder <restaurant>/<dish> --sha <dish sha>` and takes the highest entry at exactly the dish's SHA. `D1: landed as E1 (<commit>); mark it merged`: run `$B dish D1 --state merged`. `D2: E3 awaiting merge <pr url>; watch that PR`: call `watch_pull_request` on that PR, because this thread owns it even when a sibling's `land` opened it. `D4: E4 bounced: <reason>`: follow the bounce step in Run a service step 7. `D5: E6 already submitted; mark it queued`: run `$B dish D5 --state queued`. That recovers a crash between `submit` and `dish --state queued`. `D6: E7 queued` needs nothing until the queue moves.
2. "report written, no report-back" is a defect. The report file exists and the dish is not marked reported. Read the worker thread with `t3_thread_read` and `view` set to "activity". Find why the message never arrived. The brief lacked the send step, the worker skipped it, `t3_thread_send` failed or went to the wrong thread, or the timebox ran out. Fire a fix at that cause, in the brief template, the skill text the worker followed, or `brigade.py`, using the **correct** skill.
3. A "report written" line that does not say "no report-back", and a line `D<n>: reported Nm ago, not in review; read the thread (thread <id>)`, mean the worker reported back and its review has not started. Read the worker thread with `t3_thread_read`. If its run is still open, call `t3_thread_interrupt`, then `$B hang <id> --provider <provider> --minutes <n>` with the provider and the minutes that read showed. Then review per Run a service step 6. At `digest` this is a routine wake with no reply.
4. "over its timebox": `t3_thread_read` the thread with `view: "activity"` and `afterPosition`. When it made progress in the last 10 minutes, raise the timebox once with `$B dish <id> --timebox <m>`. Otherwise interrupt it and launch a fresh worker per steps 4.3 and 4.4 with a smaller scope, or park the dish with `86 add` when only the user can unblock it. Step 4.4 records that thread, and recording it starts the attempt while the dish stays in progress. The previous report and report-back mark belong to the worker you replaced. `$B watch` compares the report file to this new start.
5. `D<n>: running Nm of Tm (thread <id>)`: `t3_thread_read` the worker thread. When `activeRunId` is null and the latest run in `recentRuns` completed more than 10 minutes ago, the worker is idle with its timebox still open and no report for the current attempt. The `running` line already means that, even when an earlier attempt's report file remains, because `watch` compares the report to this attempt's start. `brigade.py` reads only the store, the clock, and the landing queue, so this check needs the thread read. Send the worker one nudge with `t3_thread_send`, mode `"auto"`, `clientRequestId` `nudge-<dish>-<that run id>`, and the line `Your thread has been idle since <completedAt> with no report. Check each open child with task_status, handle a stalled one per the runtime's Delegation step 5, then finish the brief.` A worker idle again after that nudge goes to step 4 as if over its timebox. Otherwise the line means nothing new. Reply per Run a service step 9. When `$B status` prints `reports to <thread>`, end the turn with no assistant text at all, whatever the level. Otherwise, at `milestones` this is a routine wake, so end with no reply, or with a single line when the host requires text. At `digest`, end the turn with no reply text at all. At `every-turn`, send a short reply. A dish in `in-review` or `passed` is not this line.
6. When `$B watch` prints "no work in progress", delete the liveness schedule with `delete_scheduled_task`, then run `$B set --schedule liveness=`. Delete it only on that line. A blocked ticket is not that line, so the schedule stays while one is waiting. Do not treat that line as a drained batch. A batch has drained when step 9 says it has. The drain summary is sent on the wake that first finds the batch drained. A review still pending means the batch has not drained. Do not send the drain summary while a review is pending. Reply per step 9.
## Agent activity
When the user asks what the agents are doing, or asks for activity or status as a picture, show the activity page. That request is a message from the user, so a reply is due at every reporting level. The script builds the page from this store and T3 Code's state database.
```bash
A() { python3 "<skills>/brigade/scripts/activity.py" --at "<restaurant dir>" "$@"; }
A # one HTML document on stdout, for the last 3 hours
A --hours 12 # the last 12 hours
A --text # plain lines instead of the HTML document
```
1. Run `A`. Its stdout is the whole document.
2. Call `html_preview` with that document. When the preview shows a console error or a clipped or overlapping element, send the stdout of `A --text` and name the fault in the reply. Render only a page whose preview passes. Call `html_render` with the document unchanged and the title `Agent activity`, per steps 3 to 4 of [Visual reports](../pstack-runtime/SKILL.md#visual-reports).
3. When the preview shows `This copy differs from what the tool wrote`, the `html` argument is not the script's output. Run `A` again and call `html_preview` with its stdout.
4. When `html_preview` or `html_render` is missing, or `html_render` fails, send the stdout of `A --text` as the reply.
5. On exit status 1 or 3 the script prints one line on stderr and nothing on stdout. Status 3 means it could not use T3 Code's database. Send that line as the reply. Status 1 names a fault in the arguments or the store.
The page names work items by id at every reporting level. Every string of the document and of the text form passes the script's filter, which removes each word it reads as a T3 Code thread or sub-agent id, an absolute or home-relative file path, a `file:` address, or an email address. `A --help` states what it reads as each.
## Executive chef's view
`python3 <skills>/brigade/scripts/brigade.py walk` groups every restaurant under its repository. Each repository gets one header with its landing queue's status line, or `no landing contract`. Under it, each restaurant gets its reporting level and counts, with the waiting tickets whose block still holds counted as blocked. A second line names its thread ID and the unreleased leases its dishes hold. Open decisions follow. `walk --repo <root>` prints one repository. A restaurant idle past 24 hours is marked, so a stalled head chef shows. `$B status` prints the reporting level, the landing mode as `lands by <mode>` or `no landing contract`, then the counts. A `restaurant.json` with no `reporting` field reads as `milestones`. `$B status` prints `mode <m>` when `restaurant.json` records one. `walk` adds `, mode <m>` after the reporting level.
## Close a restaurant
First, for every dish that still holds an active lease, run the three drop steps in Run a service step 7: `t3_thread_interrupt`, then `t3_thread_wait`, then `$B dish <id> --state dropped --stopped <run id>` only after a terminal state. `$B close` writes reports and releases nothing. When any wait returns `timedOut: true`, stop here. Keep every schedule, so `watch` keeps that lease renewing, and continue closing on a later service once every wait has reported a terminal state. Then call `unwatch_pull_request` on each PR this thread is still watching. Delete its schedules with `delete_scheduled_task`, then clear each recorded name with `$B set --schedule <name>=`. Run `$B close` one last time, or `$B close --to-file` at `digest`. That close is the closing reply step 9 names. Unpin the thread with `t3_thread_organize`. Leave the store. It is the record.
## Reporting to an executive admin
A restaurant reports through the repository's executive admin while `$B status` prints `reports to <thread>`. What the admin does, its side of each line below, and its rulings are in the [brigade-admin skill](../brigade-admin/SKILL.md). [What the admin never does](../brigade-admin/SKILL.md#what-the-admin-never-does) lists its limits. The user can still write to this thread directly. In the lines below, `<restaurant>` is this restaurant's directory name.
**Events.** Send each line to the admin's thread with `t3_thread_send` and mode `"queue"`, so it never interrupts a turn in progress. Send every event, whatever this restaurant's reporting level. The table's last column says which lines also leave a row in this restaurant's `log.tsv`. `failed`, `drained`, `report`, `reply`, `contest`, and `appeal` leave none, so they are message-only. How the admin reads each kind is in [Admin service](../brigade-admin/SKILL.md#admin-service) step 5. When a send fails, reply to the user about that event per Run a service step 9: at this restaurant's own level for a line with a store row, and at every level for a message-only line. A send to an admin thread that no longer exists is a failed send, so the user still hears every event.
| Line | Send when | Store row |
| --- | --- | --- |
| `merged <restaurant>: <title> [<pr url>]` | work lands. `push` and `local` modes have no PR URL | `dish` `merged` |
| `sent-back <restaurant> D<n>: <one line>` | a review sends work back or blocks it | `dish` `sent-back` or `blocked` |
| `decision <restaurant> Q<n>: <question> Options: <options>. Default: <default>.` | it parks a decision for the user | `decision` `open` |
| `failed <restaurant>: <one line>` | a failure or a paused queue it cannot fix itself | none |
| `drained <restaurant>: <closeout path>` | its batch drains. Run `$B close --to-file` for the path | none |
| `report <restaurant>: <closeout path>` | its evening report is written with `$B close --to-file` | none |
| `blocked <restaurant> T<n>: waiting on L<n> held by <holder>[ and L<n> held by <holder>] since <time>` | a ticket has been blocked for more than an hour. Name every lease it waits on, joined with `and`. `<time>` is its first `blocked` row in `log.tsv` | `ticket` `blocked` |
| `misrouted <restaurant> T<n>: <why>` | a routed ticket is off the menu, after `$B ticket move T<n> --to .admin` | `ticket` `moved` |
| `reply <restaurant>: <one line>` | it answers a `from-user` request, including a decline and its reason | none |
| `contest <restaurant> C<n> D<n>: <one line>` | its item and another coordinator's would conflict in the queue, or its passed item depends on another coordinator's unlanded item, after it opened contest `C<n>`. Name which depends on which | none |
| `appeal <restaurant> R<n>: <why>` | it disagrees with a ruling, after it complied, or instead of complying when compliance would be irreversible | none |
Send `blocked` from the Liveness check, once per ticket, with `clientRequestId` `blocked:<store path>/T<n>`, such as `blocked:app/docs/T4`, where the store path is the last two parts of `<restaurant dir>`, so a later check that sends it again delivers nothing new.
**Requests.** The admin sends each request as [Admin messages](../brigade-admin/SKILL.md#admin-messages) says. `$B inbox take` prints it as `A<n>: <line>`. Act on it, then run `$B inbox done A<n>`. A crash between the two replays the request, so key every action by its id.
| Line | Do |
| --- | --- |
| `from-user <restaurant>: <the user's words>` | Treat it as a message from the user. Work it asks for becomes `$B ticket add --summary "..." --request A<n>`, which refuses a second ticket for that id. Answer with the `reply` line. |
| `answer <restaurant> Q<n>: <answer>` | `$B 86 answer Q<n> --answer "<answer>"`. When it prints `Q<n> still open`, the item stays held, so launch and spawn nothing for it. When it prints `Q<n> answered`, act on the answer. |
| `reports-to <restaurant> <thread>` | `$B set --reports-to <thread>`, or `$B set --reports-to ""` when `<thread>` is `none`. On a new thread, create the hourly service schedule from First service step 3 when `restaurant.json` records no `intake` schedule. On `none`, delete that schedule if the menu names no intake source. |
| `ruling <restaurant> R<n>: <decision>` | Read the ruling's state with `python3 <skills>/brigade/scripts/brigade.py --at <restaurant dir>/../.admin rule list`. When `R<n>` is not `in-force`, do nothing. Otherwise carry out this restaurant's side. |
The `ticket <restaurant>: run ticket take` line from the admin is a routed ticket, and `inbox take` files it. Take it per Run a service step 2. When it is off the menu, move it back with `$B ticket move <id> --to .admin` and send `misrouted`.
**A ruling's side.** A coordinator complies with every ruling, and may appeal.
- Contested paths. The admin reserves the paths as [Rulings](../brigade-admin/SKILL.md#rulings) says. The winner fires when `watch` prints `unblocked`. The other side starts no new work on those paths.
- Ownership. When the ruling gives this restaurant's ticket to another coordinator, run `$B ticket move <id> --to <winner>` and send the `ticket` line per Run a service step 2.
- Shares. `fire` refuses a claim past this restaurant's share. Nothing else changes.
- Queue order. `land` holds the second holder's entries until an entry of the first holder lands. Nothing else changes.
When this restaurant disagrees, it complies first, then sends `appeal`. When compliance would be irreversible, such as dropping work or deleting a branch, it holds and sends `appeal`. What the admin does with an appeal is in [Rulings](../brigade-admin/SKILL.md#rulings).
**Contests.** When this restaurant's item and another coordinator's would break or conflict with each other in the queue, or when this restaurant's passed item depends on another coordinator's item that has not landed, run `$L contest --holders <restaurant>/D<n>,<sibling>/D<n> --owner <restaurant>/@<generation>`, such as `--holders docs/D4,core/D2 --owner docs/@1`. Each holder is the one `submit` takes: the restaurant's directory name, a slash, and the dish id. Read the other holder from `$L lease list`. It prints `C<n>`, and `land` holds both holders' entries until the admin settles it. Then send the `contest` line, naming which item depends on which. A contest also holds entries submitted after it, so open it before the dependent dish's `submit` and `land`, then submit per Run a service step 7. The coordinator opens the contest because it knows the dependency. How the admin orders the two, and when it opens a contest itself, is in [Rulings](../brigade-admin/SKILL.md#rulings). A refusal that says `there is nothing left to order` means one side already landed or is landing. Send nothing.
**What the user hears.** While `reportsTo` is set, reply to the user only in a service the user started. Every other wake ends with no assistant text at all, not even a one-line status, as Run a service step 9 says. This restaurant's reporting level decides only the reply for a failed send of a line with a store row. A failed send of a message-only line gets a reply at every level. `$B close --to-file` still writes each report into this restaurant's store. Once `reportsTo` is cleared, reply at this restaurant's own level again.