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

GateOnWhat you doWhat Carol does next
Accept a proposalThe proposal issueAdd carol:acceptedspecify, once the row is ready with a Design cell. A row with no Design cell gets a design session.
Mark the row readyscope.mdSet the row's Status to ready and fill its Design cell, or merge the design pull requestNothing until then. specify sets Status to running, never ready.
DesignThe design pull requestCheck the record and the row, and merge it by hand, or close it to declinespecify on the next tick
Answer questionsThe pull request, proposal or ticket issueReply, or add carol:take-recommendedReruns the step with your answers
Research a ticketThe research ticket's issueAdd carol:acceptedAn agent session opens the findings pull request. Merge it by hand. See Tickets in the loop.
Answer a grilling ticketThe grilling ticket's issueReply to Carol's questionRemoves carol:waiting. You record the answer in the ticket and scope.md.
PRD review (h2)The PRD pull requestApprove, or request changesMerges it, re-publishes, then runs
Run review (h3)The run pull requestApprove, or request changesMerges it, after the review session's verdict
Agree a contractThe contract issueAdd carol:agreed:provider or carol:agreed:consumerCounts the contract as a met dependency
Sign off a [manual] checkThe task issueComment 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.
ReleaseThe release pull requestMerge it by hand, or close it to declineTags v<version> on the merge commit, so the release workflow publishes it
Phase reviewThe phase-review pull requestCheck the work and merge it by hand, or close it to declineThe 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
KeyGate
designA design record
h2The PRD pull request that specify opens
h3The run pull request that run opens
releaseA release
phase_reviewThe phase review
contractA 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. … or 1) …), or
  • adds carol:take-recommended to 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
KeyDefaultCaps
in_flight1How many epics may be carol:running at once. A due run waits until one reports.
proposals_per_tick3How 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