Status: Phase 0, plus a Phase 1 report. Nothing here blocks, labels, comments on or merges anything. It adds a way to file agent tasks, a label set, a PR template, a workflow that reports a risk tier (T0 to T3) for each pull request, and a workflow that reports whether that PR's tip is reviewed. The rest of the plan is described so that later phases fit the constraints.
Decisions by the maintainer, and what follows from them:
- No GitHub Apps. The deterministic controller runs in GitHub Actions on the built-in
GITHUB_TOKENwith the least permissions per job. It has no LLM and no secrets. - No Cursor GitHub App on ThinkFlowLab, so no Cursor cloud agent on this repo. LLM agents
(triage, implement, review) run outside Actions, as the Chief's workstations — Triage, Dev and Reviewer — on
the team's own box. The review is done by the Reviewer Bot (acting as
Birfy, self-review); OpenCode and Codex are gone (owner decision 2026-10-22), and no workflow, routine or gate requests or waits for them. - Agents act only on issues labeled
agent-ready. That label is set by a maintainer, or by the owner's maintenance routine after triage (the routine runs as the maintainer accountBirfy, so the controller sees a maintainer's label). No LLM develops other issues, and nothing outside text sets the label. - Merge by tier (owner decisions 2026-10-19, revised 2026-10-22): T0 and T1 merge through the merge queue once CI is green, the PR is
mergeable and it conforms to the design rules, with no PASS. T2 and T3 need a PASS at the tip from the Reviewer Bot and green CI, and then the agent that gated the PR enqueues it itself — T2 and T3 need no maintainer approve (user decision 2026-10-22;
AGENTS.md→ Merge gates). A self-PASS counts only across Bots: the account is shared, and the implementer never reviews its own pull request. The gate is the review: greentests-okat the tip, a PASS at that same tip, and a test that fails when the change is reverted. Work that meets neither the defect nor the routine-need bar gets no PR at all (AGENTS.md). An issue that already has a pull request keeps it: update that branch (re-base,update-branch, or a commit on its head) rather than opening a second PR; a branch that cannot be pushed is the only exception, and the reason goes on the issue. - Budget is not set. Every cost cap will live in one config file (open PRs, attempts per PR, PRs per day, minutes per attempt) so it can be changed without code.
issue (agent-task form) --maintainer or maintenance routine adds agent-ready--> agent (outside Actions) --branch + PR-->
Actions (GITHUB_TOKEN, no LLM): tests-ok, risk tier --T0/T1--> merge queue --T2/T3--> PASS at the tip
| Role | Runs where | Credential | Can do |
|---|---|---|---|
| Controller: tier, labels, enable auto-merge, watchdog | Actions, base-branch code, no PR code run | GITHUB_TOKEN, per-job least permissions |
label, comment, gh pr merge --auto --squash |
| Agents: implement, review | the Chief's workstations on the box — Triage, Dev, Reviewer (the maintenance routine runs as Birfy) |
the maintainer account's own gh login; a narrower credential remains the goal |
triage and set agent-ready, push a branch or fork, open a PR, comment, review, enqueue a PR that may merge |
| Merge | the required GitHub merge queue on main (ruleset main merge queue) |
none: the queue merges when the required checks pass on its entry | squash only (set by the queue) |
Maintainers (Birfy, hsliuustc0106) |
GitHub | own accounts | hold (human-only), set agent-ready, pause; no T2/T3 approve |
The owner's routines run as the maintainer account Birfy; that is how things work today, and it is allowed for the
actions in this document. Agents must never admin-merge, bypass a gate or branch protection, or change rulesets
with that login (a merge goes only through the merge queue). A narrower credential (GH_TOKEN plus a separate
GH_CONFIG_DIR for the agent process) stays a goal, not a requirement. Nothing in this repo creates tokens or secrets.
| Limit | Consequence | Workaround |
|---|---|---|
Events it causes do not start workflows (except workflow_dispatch and repository_dispatch) |
a label, comment, push or PR made by an Action does not trigger other workflows; chains of "label, then workflow" do not work | do the whole decision in one job; poll on schedule; chain explicitly with gh workflow run (dispatch) |
A merge or tag made with it may not start push workflows (the Test run on main, release.yml) |
documented rule, not tested here (see Merge plumbing): an auto-merge enabled with GITHUB_TOKEN is reported to merge silently |
enable auto-merge with the agent's own credential, never with GITHUB_TOKEN; keep release.yml waiting for a green run on the tagged commit, and start Test by workflow_dispatch on main if the push run is missing |
Read-only and no secrets on pull_request from forks |
a pull_request job cannot label or enable auto-merge for a fork PR |
run writers from schedule or pull_request_target with base-branch code that never checks out or runs PR code and reads the PR through the API only |
Cannot approve PRs (can_approve_pull_request_reviews is off here) |
the controller can never approve T3 | harmless: T2/T3 need no approve (user decision 2026-10-22) |
Cannot change .github/workflows/** |
an Action cannot edit workflows | intended |
| About 1,000 API requests per hour per repository | a chatty controller stalls | few calls per tick, filter by label |
schedule can lag by minutes and public repos' schedules stop after 60 days without activity |
polling is not instant | acceptable; workflow_dispatch to run now |
| A queue merge is made by GitHub, not by the token that enqueued it | a merge by the queue does start the push run on main (observed on #217, #235); the GITHUB_TOKEN-enabled auto-merge case is still untested |
enqueue with the agent's own credential (gh pr merge --auto --squash) |
| File | Purpose |
|---|---|
.github/ISSUE_TEMPLATE/agent-task.yml |
form for a small, specified change; applies agent-task, never agent-ready |
.github/labels.json, .github/scripts/sync-labels.mjs |
label definitions and an idempotent script (dry run by default) |
.github/pull_request_template.md |
Fixes #, what changed, tip SHA / ran / not run (kept short: the body becomes the squash message) |
.github/agent/risk-rules.json, .github/scripts/risk-gate.mjs, .github/scripts/*.test.mjs |
the tier rules, the script, and its tests (run by npm run verify and the CI package job) |
.github/workflows/risk-tier.yml |
reports the tier in the job summary; continue-on-error, never required |
CODEOWNERS is not added. GitHub requests a review from every owner on every PR that touches an owned path. Seven in ten of the last 30 merged PRs touch a T3 path, so it would notify both maintainers on most PRs while enforcing nothing. Add it together with the "require review from code owners" rule when the tier is enforced (Phase 2).
- A maintainer files the Agent task form (label
agent-task). The form cannot addagent-ready: GitHub applies template labels whatever the author's permissions. - A maintainer, or the owner's maintenance routine after triage (it runs as
Birfy), addsagent-ready. That label is the only permission an agent has. Setting it again after someone else edits the issue is a manual rule now; the label-only controller enforces it (dry run untilCONTROLLER_LIVEis set). - The agent claims it (
agent-working), opens a PR withFixes #N(agent-pr), and stops atneeds-humanwhen stuck.holdfreezes an issue or PR for agents and automation.
Create the labels once, as a maintainer, with your own gh login (nothing runs this automatically):
node .github/scripts/sync-labels.mjs --repo ThinkFlowLab/FlyBot # dry run: prints the plan
node .github/scripts/sync-labels.mjs --repo ThinkFlowLab/FlyBot --apply # create or update; never deletesThe tier is the highest of: the path of every changed file (and a renamed file's old path), content
hits in the added lines of code files, and a size cap. It comes from the diff, never from the PR text.
An unnamed path is T2, never T0 or T1. A diff GitHub cannot show, or a list of 3000 files, is T3.
Generated files (core/client.js, patches/**) are not read or counted; patches check verifies them.
| Tier | Rule (owner decisions 2026-10-19) | Examples |
|---|---|---|
| T3 | a PASS at the tip (a self-PASS counts across Bots) and green CI; the gating agent merges it itself through the queue; no maintainer approve (user decision 2026-10-22; AGENTS.md → Merge gates) |
.github/**, AGENTS.md, package.json, ds-bot/**, trust-boundary host files (authz, channel, reach, ...), plugins/social/src/**; child_process, eval, new URL hosts, token env vars, .skip/.only, fence/sanitize/display-name code; over the size cap |
| T2 | a PASS at the tip (a self-PASS counts across Bots) and green CI; the gating agent merges it itself; no maintainer approve (AGENTS.md → Merge gates) |
other core/src/host/**, plugins/**, unknown paths |
| T1 | merge queue on green CI; no PASS | core/src/client/**, tests, generated files |
| T0 | merge queue on green CI; no PASS | docs/** (except policy docs), READMEs |
The rules are in .github/agent/risk-rules.json; changing them is T3. Tests may quote URLs and fence
words without a hit; .skip/.only still count in tests.
Run it by hand (read-only API calls with your gh login):
node .github/scripts/risk-gate.mjs --pr 96 --repo ThinkFlowLab/FlyBot
node .github/scripts/risk-gate.mjs --replay 30 --repo ThinkFlowLab/FlyBot # tiers of the last 30 merged PRsReplay when this was written (last 30 merged PRs, #41 to #96): T0 1, T1 2, T2 2, T3 25. Of the 25: 14 trust-boundary paths, 7 CI or agent-rule paths, 2 content hits only, 2 size only. So for FlyBot's current mix most PRs are T3 and carry a PASS; no tier needs a human click (user decision 2026-10-22). Review the tiers by hand before enforcing anything: the four PRs decided by content or size alone (#60, #63, #74, #76) are the ones to argue about.
pull_request event, contents: read and pull-requests: read, no secrets, no pull_request_target.
The script and rules are checked out from the base commit and the PR is read through the API, so
a PR cannot change the rules that judge it. The one exception is the PR that first adds the script
(base has none), which falls back to its own copy under the same read-only token; the fallback step can be
deleted once the script is on main. The workflow must stay out of branch protection and out of the
needs of tests-ok; a test checks that.
To switch the report off: disable the workflow in the Actions tab, or delete .github/workflows/risk-tier.yml.
A merge decision needs a check it can read, not a report in a chat. .github/scripts/review-gate.mjs
answers one question for a pull request: does the current head carry a Verifier PASS?
AGENTS.md step 6 is the rule it serves. A PASS is required for T2 and T3 (at the tip, with green CI);
T0 and T1 need no PASS and merge on green CI; the merge gate computes that tier itself from the
changed files with the base copy of risk-gate.mjs and risk-rules.json, never from the risk:T*
labels (#640: a shared account makes a label a claim, not a fact). The agent that gated the PR then
enqueues it itself (owner decision 2026-10-21). This report is the evidence of a PASS; it never merges anything.
What counts as a reviewed tip, all of it:
- A pull request comment or review body by a login listed in
.github/agent/review-policy.json. - Not the pull request's own author, unless that author is listed in the policy's
selfReview(today:Birfy). Owner decision 2026-10-19: a PR opened by the owner's Bot/agent may be PASSed by the owner's own reviewer Bot/agent, so no independent reviewer is required. That is an exception about the shared account, not about the Bot: an implementer never reviews its own pull request (AGENTS.md→ Merge gates). Any other author still needs someone else. A PASS does not replace green CI, and a new commit voids it. - The conclusion format is fixed (#637): the first line is
Verdict: PASS at <sha>(orINC/REQUEST_CHANGES), the second is the attribution (Written-by: Reviewer; a relay addsPosted-by: Chief (proxy…)on the third line, and the older proseWritten by: …reads the same way), then the evidence —Ran:(or aRan …:heading over a fenced block of$commands, #660–#663) with at least one command;Not run:naming any gap is recommended but not checked (owner decision 2026-10-22 09:57 Asia/Shanghai). A body whose first line is not the verdict is no conclusion. The comment'sauthor_associationmust beOWNER,MEMBERorCOLLABORATOR, or the report must be that pull request's own author listed inselfReview(#928), or the login must be shown as a repo admin by the permission API. Org repositories do not reportOWNER. ACONTRIBUTORwho is none of those counts for nothing, so they can neither pass a tip nor hold one. An association the API did not send is not assumed, and a failed permission lookup is not admin. The permission API is for an admin GitHub did not tie to the repository: aCONTRIBUTORstamp on someone else's pull request, asked only for a Tip+Verdict conclusion by a login inreviewers. AselfReviewlogin is skipped only on that author's own report, which is the #928 carve-out and needs no lookup — skipping everyselfReviewlogin would never call, because the shipped policy lists every reviewer there. That lookup does not recover the own-pull-request PASS; #926 and #928 already do. The two qualifications do not replace each other:selfReviewanswers only whether an author may review their own pull request and gives that login no right on anyone else’s, while admin is read only through the permission API and only for a login already inreviewers. Every other check is unchanged. - The sha on that first line is the head being judged. A later commit moves the tip and the old PASS stops counting; that report is then listed as "not counted", with the sha it named.
- The newest report that names this tip decides, so a later FAIL at the same tip vetoes an earlier PASS, and a later PASS at the same tip replaces a FAIL.
- A GitHub bot account writes no conclusion (#943):
user.typeisBot, or the login ends[bot], and the report joinsjudgedwithcounts: falseand a reason naming the account, whatever its body says — the real Reviewer voids it at that tip withCorrection from Reviewer:. The rule is the account type and not a login list: every Bot writes as the sharedBirfyaccount, so a list containing it would void the whole pipeline.cursor[bot]'s report at #940's tip is the case (Correction from Reviewer, comment6381646389).
Attribution (#627). Every state-changing write says who wrote it: Written-by: <Bot> is its first
line, and a conclusion puts it on the second, under the fixed verdict line. decide refuses a report
with no such line and, separately, one whose name is not in the policy's writers; Triage reports a
write without one instead of editing it. The rule itself lives in AGENTS.md — one shared GitHub identity
makes the line a self-report, so the gate guarantees presence, not truth (#633).
| File | Purpose |
|---|---|
.github/agent/review-policy.json |
the logins whose PASS counts, selfReview, the authors who may PASS their own PR, and writers, the Bot names a Written-by: line may carry (#627); editing it is T3 |
.github/scripts/review-gate.mjs |
the rule above, for one pull request at a time |
.github/scripts/review-gate.test.mjs |
stale tip, self-review (allowed only for a selfReview author), unlisted login, FAIL after PASS, missing Ran: (a missing Not run: no longer refuses), a missing Written-by: line and one naming an unlisted writer (#627), a selfReview login counting only on its own pull request (#928), a selfReview admin on someone else's pull request (permission API, CONTRIBUTOR stamp), a CONTRIBUTOR who is neither still refused, and the workflow's shape |
.github/workflows/review-gate.yml |
reports it in the job summary; continue-on-error, never required |
Run it by hand (read-only API calls with your gh login):
node .github/scripts/review-gate.mjs --pr 215 --repo ThinkFlowLab/FlyBot
node .github/scripts/review-gate.mjs --pr 215 --sha <other head> # judge a tip that is not the headThe workflow is safe the same way risk-tier.yml is: pull_request (never pull_request_target),
a read-only token, no secrets, and the script and policy checked out from the base commit, so a
pull request cannot change the rules that judge it. The one exception is the pull request that adds
the script: the base has none, so that run falls back to the PR's copy under the same read-only
token. It is not a required check and must stay out of branch protection and out of tests-ok's
needs (a test checks the needs half; branch protection is a live setting), so a pull request
without a PASS still needs its own review before the gating agent merges it.
Why report-only. The owner's reviewer Bot/agent acts as Birfy, which is in the policy (and in selfReview), so
its report can satisfy the gate on a pull request the same account opened — the review comes from a different Bot than
the implementer (AGENTS.md → Merge gates). Nothing here blocks or merges: it is evidence an
agent reads, and the record a later enforcement step is checked against.
Silent revert (#914). A pull request built on an old copy of main re-uploads whole files, and work main
merged after the branch was cut disappears — with mergeable: true and tests-ok green, because the deletion is
the pull request (#887 deleted #869's 84 test lines the same hour; #907 deleted #909's whole fix 40 minutes
after it merged). .github/scripts/silent-revert.mjs reads the rule off git:
base = merge-base(origin/main, HEAD), A = the lines origin/main added after base, D = the lines the pull
request deletes from origin/main, and A ∩ D ≠ ∅ is a silent revert — reported per file with the count and the
commit that added the line to main. It runs as a step of merge-gate.yml on pull_request only, from the
base copy of the script, with a full-history checkout of the head (fetch-depth: 0 plus an explicit
origin/main fetch), and writes one ::warning per file and a total line silent-revert: N finding(s) on M file(s) to the job summary — including silent-revert: 0 finding(s) on M file(s) when it is clean, so a run that
read nothing is not confused with a run that found nothing. Report only: continue-on-error, never a comment, a
label or a required check, so a legitimate revert carries the report and a Reviewer decides. A pull request already
rebased onto main has A empty and reports zero by construction — which is also this rule's boundary: a rebase
that resolves a conflict by keeping the older side deletes the same lines but has main as its base, and stays
invisible to it (#907's d6e143a8 is that case). The check catches the stale copy, not the bad merge.
main requires a merge queue (repository ruleset main merge queue, active since 2026-10-17). test.yml has a
merge_group: trigger, so every queue entry runs Test.
The gates themselves — hold, the newest-conclusion rule, and the merge-gate job that enforces both on a queue
entry — are in AGENTS.md → Merge gates. This section is only the plumbing.
How to merge. A direct gh pr merge <n> --squash is refused with ! The merge strategy for main is set by the merge queue; that is not a failure. Run node .github/scripts/enqueue-guard.mjs --pr <n> first — it re-reads the live head and
exits 1 on a hold, on a newest conclusion that is not a PASS, and on a report marked "record only — do not merge", so a
PASS is never a queue permit on its own (#602); on a live, non-draft pull request of ours it also refuses a body with no
Owner:/Reviewer: line in its first 20 lines (or a Reviewer: outside reviewers), naming the missing line or the
login (#943) — a merged pull request is not re-judged, because this check also runs on every comment; on success it prints the command and never runs it. Enqueue with
gh pr merge <n> --auto --squash --match-head-commit <sha> (gh has no --merge-queue flag). gh may still print the
strategy line, or ! Pull request #<n> is already queued to merge; the PR is
then in the queue (gh api graphql → pullRequest.isInMergeQueue). The queue, not the agent, performs the squash merge
once Test passes on the gh-readonly-queue/main/pr-<n>-<sha> entry; do not retry. The queue waits up to 5 minutes
to fill a group and builds one entry at a time.
The patrol that notices a bypass. .github/workflows/audit-merges.yml runs audit-merges.mjs against main daily
and on workflow_dispatch, read-only. --since is the head_sha of its own previous successful run, so a merge that
skipped the queue prints as bypassed queue: and fails the run while an older one prints as earlier bypass: and does
not — the watermark lives in the Actions API and nothing is committed. Its first run passes --baseline, so the first
green run is the baseline and only a newer bypass turns it red (#697).
The fork Birfy/FlyBot is where this team's pull requests are actually merged, and it had neither a ruleset nor branch
protection: merge-gate.yml reports on pull_request and never fails there, pass-label records a PASS and is not a
gate, and enqueue-guard.mjs only refused in a script somebody remembered to run. So a PASS was advisory, and #30
merged without agent-pr.
.github/workflows/enqueue-guard.yml writes the guard's own decision onto the pull request head as a check run named
enqueue-guard, and .github/agent/merge-gate-ruleset.json is the ruleset that requires it next to tests-ok on
refs/heads/main, with bypass_actors: []. Two properties make it a gate rather than a badge:
- One definition. The script calls
decidePullinenqueue-guard.mjs— the same function behind the command the guard prints — so a rule added to the merge gate is enforced by the check without being restated. Green means exactly "the printed command would say ALLOW". - It flips without a commit. A verdict is a comment and a label is a label: neither starts a
pull_requestrun for the head being judged, which is why the check is written through the API instead of being a job's own result.external_idis the head, so a re-judgement PATCHes that one run; a new commit is a new head and therefore a new run, and a green check never carries over.
pull_request_target is the trigger, not pull_request: a pull_request job from a fork is read-only and could not write
the check, while a required context that is never produced leaves that pull request on "Expected" forever (#639). The code
that judges is checked out from main (ref: main), no pull request code is fetched or run, and the only thing read from
the event is the number.
Turning it on is sequenced, and it is the owner's call. node .github/scripts/gate-ruleset.mjs --check reads the live
state and writes nothing; --apply creates or converges the ruleset and --rollback deletes it and proves it is gone —
both change repository protection, which no Bot does on its own (AGENTS.md → Hard rules). The order is: merge the
workflow, prove the check goes red on a pull request that is missing agent-pr and green on one that is complete, and
only then apply the ruleset. Applied first, every open pull request reads "Expected — waiting for status" and none can
merge. --apply runs --check's assertions on what GitHub read back (bypass_actors, current_user_can_bypass: never,
both contexts, strict_required_status_checks_policy: false so one merge on main does not force every open pull
request to rebase).
The patrol that keeps the labels honest. A status label says what is happening to an item now, so it must not
outlive the item: on 2026-10-22 the board counted 11 closed issues and 43 closed pull requests as in flight, because
nothing removed agent-working/agent-pr when they closed. .github/workflows/label-hygiene.yml runs
label-hygiene.mjs daily and on workflow_dispatch, and it removes exactly the status labels in its STALE_LABELS
list — bug, area:*, risk:*, hold, needs-* and status:done are records and it never touches them, because
hold stays human-only in both directions. It asks once per status label (GitHub's labels= filter is an AND, so one
query for five labels would only find an item carrying all five), prints the before: count, applies, then reads the
repository back and prints after: — a non-zero after: fails the run, so the cleanup is verified rather than
trusted (#738).
What the trigger does. A merge queue builds each entry on a gh-readonly-queue/main/pr-N-<sha> branch (a merge of
the PR onto the current main, plus the entries ahead of it) and merges only when the required checks pass on that
commit. Required checks are matched by name on that commit, so Test must run on merge_group; otherwise tests-ok
and the test job never report there and the entry waits until the queue's timeout. With the trigger:
test,plugins (tree),plugins (social),packageandtests-okrun unchanged. Nothing intest.ymlreads thepull_requestevent, no job has anif:that would skip it, andtests-okkeepsif: always()and needs all three, so a skipped or failed job still makes it red (.github/scripts/test-workflow.test.mjschecks all of this).merge_grouptakes no path filters, so a docs-only entry runs in full. That is also whypaths-ignorestays onpushonly.pushstays limited tomain, so a queue branch does not run twice. The concurrency group is per ref, so entries do not cancel each other, and every non-mainref (queue branches included) cancels its own earlier run.- Queue entries run
Testonly.pull_request-only workflows (risk-tier.yml, the review gate) do not run onmerge_group; that is intended, and they must stay out of the required checks, or every entry waits until the queue's timeout. - The
arch-docsstep fetchesorigin/mainand deepens untilgit merge-base origin/main HEADworks. Emulated on a one-commit shallow checkout of a merge-queue-shaped commit (local bare remote): it falls back to deepening andarch-docs: OK. With a group size above 1,arch-docssees the union of the grouped PRs; keep the group size at 1. - Observed since the queue was enabled:
merge_groupruns ongh-readonly-queue/main/pr-<n>-<sha>pass and the PR merges (e.g. #217 →38c00b4, #235 →7a3ccb3).
Read-only checks of the live repository (2026-10-15, rulesets re-read 2026-10-18; gh api GET with a maintainer login):
| Fact | Value |
|---|---|
| Repo | public, owned by an Organization |
| Merge methods | squash only; delete_branch_on_merge: true; allow_auto_merge: true; allow_update_branch: true |
Branch protection on main |
required checks: the test job and tests-ok; strict: false; no required reviews; enforce_admins: false; no force pushes |
| Required check on the queue | merge-gate is a required check of the main merge queue ruleset (user decision 2026-10-22, #639), so it is the mechanical stop for anything that tries to skip the queue: merge-gate.mjs re-judges the pull request head on every entry |
| Rulesets | main merge queue on refs/heads/main, active, no bypass actors: merge queue (squash, build and merge 1 entry at a time, 5 min wait to fill a group, 60 min check timeout, ALLGREEN) and required checks ubuntu-latest · Node 24 · DSH 0.2.0-rc.2 (the test job), tests-ok and merge-gate (job name, added 2026-10-22). The organization-level list needs the admin:org scope (not read) |
| Org plan | not readable: plan is null without admin:org |
| Workflow token | default read; Actions cannot approve PRs |
Merge queue settings. Keep the group size at 1 (see arch-docs above) and tests-ok the single aggregate check.
Changing the queue or the ruleset is a maintainer decision.
Does an auto-merge by GITHUB_TOKEN start the push workflows? Unverified here; assume no. GitHub documents that
events caused by GITHUB_TOKEN (except workflow_dispatch and repository_dispatch) create no workflow runs. Several
independent public reports say the same for gh pr merge --auto run with GITHUB_TOKEN: the merge is attributed to
github-actions[bot] and the push run on main never starts. A real test needs a PR that actually merges into
main (or a scratch base branch with its own protection), which was out of scope. Consequences and the safe design:
- The controller must not enable auto-merge (it is label-only anyway). Agents enable it with their own credential.
- If a merge ever lands without a
Testrun onmain, start one withgh workflow run Test --ref main(workflow_dispatchis exempt from the rule; needsactions: write), sorelease.ymlhas a green run to wait for. - Merges made by the merge queue itself do start the
pushrun onmain: #217 (enqueued withgh pr merge --auto --squashby an agent's own credential) merged as38c00b4and apushrun ofTeston38c00b4started and passed (#222). - To check it once, with the maintainers' consent: merge one trivial PR with auto-merge enabled by the intended
credential, then
gh run list --workflow Test --branch main --limit 3should show apushrun on the merge commit.
- Merge plumbing. The queue is on and its merges start the
pushrun (above). Still to do: test the push behavior of an auto-merge enabled withGITHUB_TOKEN(needs a scratch base branch). Conflict-proofing for generated files is a separate PR (patch format, changelog fragments, split core pages). - Controller, label-only (
scheduleplusworkflow_dispatch,GITHUB_TOKEN): demoteagent-readyafter a non-maintainer edit, setagent-prandrisk:T*, expire stale claims, a pause variable and a kill-switch drill. Enforce the tier with a required check and CODEOWNERS for the T3 reviewer-tip check. - Agents on a dedicated credential, one open PR at a time, with the cost caps in one config file.
Add any new reviewer's login to
.github/agent/review-policy.jsonin the same change: until it is listed, its report cannot satisfy the reviewed-tip gate. - Auto-merge T0/T1 on green CI (no PASS needed, owner decision 2026-10-19); T2/T3 stay behind a PASS at the tip, which the gating agent then merges itself (owner decision 2026-10-21). Then edge releases, a red-
mainwatchdog, and T2 behind a smoke gate.