Human gates
Where people decide in Carol's engine loop, how many approvals each gate needs with [gates.approvals], and how [limits] caps what one tick starts.
Carol v0.1.22
Carol moves a phase between decisions. It never makes them. Every place where a person decides stays a gate, and every gate is on GitHub: a label, a pull request review, or a reply to a question. Carol reads the decision from the tracker and acts on the next tick.
Who counts
Only an acceptor can open a gate. Acceptors are the GitHub user ids listed in [identity].acceptors (see GitHub App setup). Carol matches on the user id, not the login, so a renamed account still counts. A label or review from anyone else, from a bot, or from Carol's own App doesn't count.
Where people decide
| Gate | On | What you do | What Carol does next |
|---|---|---|---|
| Accept a proposal | The proposal issue | Add carol:accepted | specify, once the row is ready with a Design cell. A row with no Design cell gets a design session. |
| Mark the row ready | scope.md | Set the row's Status to ready and fill its Design cell, or merge the design pull request | Nothing until then. specify sets Status to running, never ready. |
| Design | The design pull request | Check the record and the row, and merge it by hand, or close it to decline | specify on the next tick |
| Answer questions | The pull request, proposal or ticket issue | Reply, or add carol:take-recommended | Reruns the step with your answers |
| Research a ticket | The research ticket's issue | Add carol:accepted | An agent session opens the findings pull request. Merge it by hand. See Tickets in the loop. |
| Answer a grilling ticket | The grilling ticket's issue | Reply to Carol's question | Removes carol:waiting. You record the answer in the ticket and scope.md. |
PRD review (h2) | The PRD pull request | Approve, or request changes | Merges it, re-publishes, then runs |
Run review (h3) | The run pull request | Approve, or request changes | Merges it, after the review session's verdict |
| Agree a contract | The contract issue | Add carol:agreed:provider or carol:agreed:consumer | Counts the contract as a met dependency |
Sign off a [manual] check | The task issue | Comment the evidence and add carol:signed (or run zurdo review --sign-off <task>:<n> yourself) | Signs it as you with zurdo review and closes the issue. See Sign-off. |
| Release | The release pull request | Merge it by hand, or close it to decline | Tags v<version> on the merge commit, so the release workflow publishes it |
| Phase review | The phase-review pull request | Check the work and merge it by hand, or close it to decline | The row is done, and propose moves on to the next phase |
An unauthorised carol:accepted or carol:agreed:* label gets one comment from Carol naming who added it. Carol never removes the label: you decide what to do with it.
Approvals: [gates.approvals]
A pull request gate opens once enough distinct acceptors have left an Approve review on its latest commit. [gates.approvals] in .carol/config.toml sets how many, per gate:
[gates.approvals]
h2 = 1 # the PRD pull request
h3 = "all" # the run pull request: every acceptor
| Key | Gate |
|---|---|
design | A design record |
h2 | The PRD pull request that specify opens |
h3 | The run pull request that run opens |
release | A release |
phase_review | The phase review |
contract | A contract |
Each value is a positive count of distinct acceptors, or "all" for every acceptor. Any key you leave out is 1. A zero, a negative number, another string or an unknown key is a config error that names gates.approvals.<key>. In v0.1.22 the loop holds h2 and h3 with these counts. The design, findings, release and phase-review pull requests are merged by hand, never by watch. The other keys are accepted and checked, ready for the steps that use them.
What counts as an approval:
- The latest review per acceptor. An acceptor who approved and then requested changes hasn't approved.
- On the head commit. An approval of an older commit doesn't count, so pushing to the branch resets the count.
- Acceptors only. Reviews by other people or by bots are ignored.
Once the count is met, watch merges the pull request as the App, with a merge commit, and deletes its branch when that branch lives in the same repository. A branch on a fork is left alone. A delete that fails prints one notice, and the merge stands. A run pull request also waits for its epic to be carol:reported and for the run review's verdict.
Requesting changes
A Request changes review from an acceptor on the head commit wins over any number of approvals. Carol labels the pull request carol:changes-requested and keeps the review's text for the next agent session, which reruns the step with it. An approval on a newer commit clears the label.
Questions and answers
When an agent session needs a decision, Carol posts its questions in one comment, numbered, and labels the item carol:waiting. Each recommended answer is its own quoted paragraph under its question:
<!-- carol:ask -->
Carol has questions before going on:
1. Which store?
> Recommended: sqlite
2. Ship the CLI first?
> Recommended: yes, behind a flag
Reply with your answers, by number, to resume; or add the `carol:take-recommended` label to take every recommendation.
Nothing else happens to the item until an acceptor either:
- replies with their answers, numbered to match (
1: …,1. …or1) …), or - adds
carol:take-recommendedto take every recommended answer.
A reply may quote the questions comment, as GitHub's Quote reply does. Carol drops the quoted lines and reads the rest as your answer.
The next tick removes both labels and reruns the step in a fresh session that reads the questions and your answers.
A grilling ticket's question is the one Carol asks without a session: its recommendation reads No recommendation:, because the decision is yours. See Tickets in the loop.
The H2 push comment
Every H2 session that pushes commits to a PRD pull request gets one <!-- carol:h2 --> comment from the App. It lists the commits it pushed and says what comes next: more questions, ready for an approving review, or halted. When an acceptor answered questions since the last such comment, it opens with each question and the answer the session applied, quoted under it:
<!-- carol:h2 -->
@ana answered:
1. Which store?
> Answer: postgres
2. Ship the CLI first?
> Answer: yes
Carol's H2 session pushed to `carol/specify-phase-02`:
- `3f1d2a9` Answer the store question
- `8c04e1b` Mark the PRD reviewed
The PRD is reviewed: an approving review merges it.
A numbered reply is split by number, one answer under each question. A reply that doesn't number every question is quoted whole, below the list of questions. With carol:take-recommended, each answer is the recommendation, and the comment opens @ana took the recommendations.
The H3 verdict
The run review's verdict is a <!-- carol:h3 --> comment from the App on the run pull request. Carol lays it out from the session's findings: a bold line with what to do now, a table with one row per task and its issue, the evidence folded away, and notes for later. When the review asks for a follow-up PRD, Carol runs it first, and the verdict ends with its result. See The run review.
The session's handoff
An H2 or H3 session ends by writing a handoff, as a person's session does, but it never commits one: a pull request's branch carries no docs/<initiative>/handoff.md from a session, so a merge can't overwrite yours. The session writes it to .carol/handoff.md in its worktree. Carol appends it to the pull request's thread, which the next session on that pull request reads, and adds it, collapsed, to the end of the H2 push comment or the H3 verdict. A design or research session does the same: its handoff goes to the thread of the proposal or ticket issue, and to the end of the pull request it opens.
<details><summary>The session's handoff</summary>
Stopped at: PRD reviewed, ready.
</details>
If a session commits the handoff anyway, Carol commits it back to what the branch had before the session, as the App (handoff: keep docs/<initiative>/handoff.md off this branch), before it pushes.
The digest
Each tick keeps one pinned issue, Carol digest, under the scope issue. It lists everything waiting on a person: open carol:waiting items, gate pull requests short of their approvals, and epics that are carol:running. Each new item gets one comment that @mentions every acceptor, so you hear about it without watching the terminal.
Halts
carol:halt stops a phase. Carol adds it to a pull request when an agent session fails, leaves the PRD draft unreviewed without asking anything, or leaves no verdict, and when a run review's follow-up run fails. A follow-up that passes with nothing new to push hasn't failed. It labels a proposal when its design session fails or leaves no graduated row, and a ticket issue when its research session fails or leaves no findings. While the epic or pull request carries it, Carol plans nothing for that phase and carol run refuses. Look at what went wrong, fix it, and remove the label to resume.
Limits: [limits]
[limits] caps how much one tick starts, so the loop never gets ahead of the people reviewing it:
[limits]
in_flight = 1 # epics carol:running at once
proposals_per_tick = 3 # new proposals opened by one tick
| Key | Default | Caps |
|---|---|---|
in_flight | 1 | How many epics may be carol:running at once. A due run waits until one reports. |
proposals_per_tick | 3 | How many proposals one tick opens. The rest wait for later ticks. |
Each value is a non-negative integer. Anything else is a config error. Limits only apply to tick and watch: a carol propose or carol run you type yourself isn't capped.
Next: GitHub App setup