Why. One reviewer is one opinion. The operator wants seats that keep each other in check and a vote that settles instead of deadlocking 1 v 1.
Council design (decided 2026-10-17).
Votes count per seat (one Gitea identity each, #76). Models within a family are distinct voters — haiku and opus can both sit — while cross-family agreement is the stronger evidence, so the standing configuration mixes families.
TIME_REVIEW is a comma-separated list of seat specs; every listed seat reviews each PR and posts as itself. Standing pair for this repo: claude:haiku, codex (Codex reviews through a headless CLI run, #79; until that lands the pair is claude:haiku, claude:opus).
TIME_REVIEW_TIEBREAK names one seat that is called only when the standing seats split (equal approves and objects, or below quorum with no objection). For this repo: venice:kimi. Venice credits are spent on ties alone.
TIME_REVIEW_QUORUM (default 1): approvals needed, net of objections.
Codex's seat ladder, comparable to haiku → fable: luna, terra, sol, astra. Add them to seats.json with the CLI route, then they are valid in TIME_REVIEW like any seat. Note the name clash with the daily planning tick ("Astra", an opus seat): the tick keeps its role name in its log line; the astra login is the Codex seat.
Change (.github/time/time.py).
review(): parse the list; run each seat; each posts via post_review(); return the seats it ran.
verdict(pr, since): take each trusted identity's latest verdict since since (Time's own comments count as one identity, gitea-actions); approve when approves minus objects reaches the quorum, object when objects exceed approves, else None.
Tie path in land(): on None with every standing seat having voted, call the tie-break seat once (one review, posted as itself), then re-evaluate; if still None, one "the council is split N–N; over to a human" comment with a needs-human beat. With one standing seat and quorum 1, behaviour is exactly today's.
Tests. Two seats Approve/Approve → merge; Approve/Object → tie-break seat is called exactly once, its Approve merges, its Object yields the split message once; a seat that objected and later re-approves → its latest vote counts; quorum 2 with one Approve and no tie-break configured → waits; the single-seat path unchanged (checks 13 and 55 keep passing); the tie-break seat is never called when the standing seats agree.
CHANGELOG line under "### Added". The TIME_REVIEW / TIME_REVIEW_TIEBREAK variables on this repo are the CEO's to set once this lands.
DONE: PR open with the code above, python3 .github/time/test_time.py green including the new checks, CI green
Depends on #76
**Why.** One reviewer is one opinion. The operator wants seats that keep each other in check and a vote that settles instead of deadlocking 1 v 1.
**Council design (decided 2026-10-17).**
- Votes count per seat (one Gitea identity each, #76). Models within a family are distinct voters — `haiku` and `opus` can both sit — while cross-family agreement is the stronger evidence, so the standing configuration mixes families.
- `TIME_REVIEW` is a comma-separated list of seat specs; every listed seat reviews each PR and posts as itself. Standing pair for this repo: `claude:haiku, codex` (Codex reviews through a headless CLI run, #79; until that lands the pair is `claude:haiku, claude:opus`).
- `TIME_REVIEW_TIEBREAK` names one seat that is called **only when the standing seats split** (equal approves and objects, or below quorum with no objection). For this repo: `venice:kimi`. Venice credits are spent on ties alone.
- `TIME_REVIEW_QUORUM` (default 1): approvals needed, net of objections.
- Codex's seat ladder, comparable to haiku → fable: `luna`, `terra`, `sol`, `astra`. Add them to `seats.json` with the CLI route, then they are valid in `TIME_REVIEW` like any seat. Note the name clash with the daily planning tick ("Astra", an opus seat): the tick keeps its role name in its log line; the `astra` login is the Codex seat.
**Change (`.github/time/time.py`).**
- `review()`: parse the list; run each seat; each posts via `post_review()`; return the seats it ran.
- `verdict(pr, since)`: take each trusted identity's **latest** verdict since `since` (Time's own comments count as one identity, `gitea-actions`); `approve` when approves minus objects reaches the quorum, `object` when objects exceed approves, else `None`.
- Tie path in `land()`: on `None` with every standing seat having voted, call the tie-break seat once (one review, posted as itself), then re-evaluate; if still `None`, one "the council is split N–N; over to a human" comment with a `needs-human` beat. With one standing seat and quorum 1, behaviour is exactly today's.
**Tests.** Two seats Approve/Approve → merge; Approve/Object → tie-break seat is called exactly once, its Approve merges, its Object yields the split message once; a seat that objected and later re-approves → its latest vote counts; quorum 2 with one Approve and no tie-break configured → waits; the single-seat path unchanged (checks 13 and 55 keep passing); the tie-break seat is never called when the standing seats agree.
CHANGELOG line under "### Added". The `TIME_REVIEW` / `TIME_REVIEW_TIEBREAK` variables on this repo are the CEO's to set once this lands.
DONE: PR open with the code above, `python3 .github/time/test_time.py` green including the new checks, CI green
teahubxyz
changed title from Council verdict: TIME_REVIEW takes several seats and the PR lands on a majority of distinct identities to Council verdict: TIME_REVIEW takes several seats, votes count per seat, and TIME_REVIEW_TIEBREAK names a seat called only on a tie2026-10-17 22:54:45 +00:00
⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch time/card-77.
⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch `time/card-77`.
<!-- time {"beat":"dispatched","launched":true,"worker":"anthropic"} -->
⏱️ Time: sonnet opened PR #122 (23 steps, claude-sonnet-5-5). Review and landing follow on the next beat.
⏱️ Time: `sonnet` opened PR #122 (23 steps, claude-sonnet-5-5). Review and landing follow on the next beat.
<!-- time {"beat":"dispatched","worker":"anthropic","pr":122,"steps":23,"seat":"sonnet","model":"claude-sonnet-5-5"} -->
⏱️ Time: PR #122 is up for this card. haiku reviewed it on the PR.
⏱️ Time: PR #122 is up for this card. `haiku` reviewed it on the PR.
<!-- time {"beat":"pr","pr":122,"launched":false,"review":"venice","seat":"haiku","as":"seat"} -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Depends on #76
Why. One reviewer is one opinion. The operator wants seats that keep each other in check and a vote that settles instead of deadlocking 1 v 1.
Council design (decided 2026-10-17).
haikuandopuscan both sit — while cross-family agreement is the stronger evidence, so the standing configuration mixes families.TIME_REVIEWis a comma-separated list of seat specs; every listed seat reviews each PR and posts as itself. Standing pair for this repo:claude:haiku, codex(Codex reviews through a headless CLI run, #79; until that lands the pair isclaude:haiku, claude:opus).TIME_REVIEW_TIEBREAKnames one seat that is called only when the standing seats split (equal approves and objects, or below quorum with no objection). For this repo:venice:kimi. Venice credits are spent on ties alone.TIME_REVIEW_QUORUM(default 1): approvals needed, net of objections.luna,terra,sol,astra. Add them toseats.jsonwith the CLI route, then they are valid inTIME_REVIEWlike any seat. Note the name clash with the daily planning tick ("Astra", an opus seat): the tick keeps its role name in its log line; theastralogin is the Codex seat.Change (
.github/time/time.py).review(): parse the list; run each seat; each posts viapost_review(); return the seats it ran.verdict(pr, since): take each trusted identity's latest verdict sincesince(Time's own comments count as one identity,gitea-actions);approvewhen approves minus objects reaches the quorum,objectwhen objects exceed approves, elseNone.land(): onNonewith every standing seat having voted, call the tie-break seat once (one review, posted as itself), then re-evaluate; if stillNone, one "the council is split N–N; over to a human" comment with aneeds-humanbeat. With one standing seat and quorum 1, behaviour is exactly today's.Tests. Two seats Approve/Approve → merge; Approve/Object → tie-break seat is called exactly once, its Approve merges, its Object yields the split message once; a seat that objected and later re-approves → its latest vote counts; quorum 2 with one Approve and no tie-break configured → waits; the single-seat path unchanged (checks 13 and 55 keep passing); the tie-break seat is never called when the standing seats agree.
CHANGELOG line under "### Added". The
TIME_REVIEW/TIME_REVIEW_TIEBREAKvariables on this repo are the CEO's to set once this lands.DONE: PR open with the code above,
python3 .github/time/test_time.pygreen including the new checks, CI greenCouncil verdict: TIME_REVIEW takes several seats and the PR lands on a majority of distinct identitiesto Council verdict: TIME_REVIEW takes several seats, votes count per seat, and TIME_REVIEW_TIEBREAK names a seat called only on a tie⏱️ Time: Claude Sonnet (Anthropic) is working on this now, on branch
time/card-77.⏱️ Time:
sonnetopened PR #122 (23 steps, claude-sonnet-5-5). Review and landing follow on the next beat.⏱️ Time: PR #122 is up for this card.
haikureviewed it on the PR.⏱️ Time: approved and green, merged PR #122. Card done.