Skip to content

Gate reasons and run endings

A lookup page for the codes you see in hand-off comments, in the room and in maku status. Why the gate works this way: the merge gate.

Terms

TermMeans
live, shadowA live class merges by itself; a shadow class is decided and recorded, never merged
holdout checkA CI check the class requires, whose workflow the agent may not edit
oracleThe scan that found the problem a class fixes; it must then report the problem gone
trailerThe Agent-Run: line in a commit, naming the run that made it
control issueThe issue where the factory posts its notices and takes its kill switch
the bypassAn admin merge (gh pr merge --admin) by a maintainer, after reading the change. Use it only for the reasons no approval returns

The decision rows

The gate reads a pull request whose CI has run, row by row; the first “no” hands it to a human. With no class live or shadow in the repository, it reads nothing and hands off as merge_gate_off.

    flowchart TB
  subgraph s1["Agent PR in AwaitingCI · rows 1–3"]
    direction LR
    base(["1–2c: secret scan,<br/>fence, required checks,<br/>test contract"])
    base -->|"red"| fix["fix run (up to fixRuns),<br/>then escalate"]
    base -->|"green"| pb(["3: policy-bot:<br/>main green?"])
    pb -->|"no"| ho1["AwaitingHuman: reason,<br/>one action, decision record"]
  end
  subgraph s2["Row 4"]
    direction LR
    back["AwaitingHuman:<br/>approve the current head"] --> appr(["4: a maintainer approved<br/>the current head?"])
    appr -->|"yes, approveToMerge"| amerge["merger merges it<br/>factory/approved-merge<br/>→ Done"]
  end
  subgraph s3["Rows 5–8"]
    direction LR
    cls(["5: class live<br/>or shadow?"]) -->|"yes"| rows(["6–8: reviewer verdict,<br/>head reported, trailer,<br/>files, size, shape,<br/>holdouts, oracle"])
    cls -->|"neither"| ho2["AwaitingHuman"]
    rows -->|"a row fails"| ho2
  end
  subgraph s4["Rows 9–12: a live class"]
    direction LR
    brk(["9: breaker clear?<br/>10: under the daily caps?"]) -->|"yes"| push(["12: every push<br/>the agents App's?"])
    brk -->|"no"| ho3["AwaitingHuman"]
    push -->|"no"| ho3
    push -->|"yes"| merge["merger merges<br/>the decided head<br/>factory/auto-merged:class<br/>→ Verifying"]
  end
  shadow["record shadow_would_arm<br/>paused= capped= read,<br/>nothing armed"]
  s1 -->|"3: yes"| s2
  s2 -->|"4: no"| s3
  s3 -->|"all hold, live"| s4
  s3 -->|"all hold, shadow"| shadow
  
#CheckIf it fails
1The secret scan (merge.leakScanCheck) is not redEscalated, secret_scan_red
2aThe repository names this factory as its ownernot_owned
2Every merge.requiredChecks is greenRed: a fix run, up to merge.fixRuns, then ci_red. Cancelled: a re-run. Pending: wait
2b, 2cA task with a test contract: its check is green, and no contract file changedAs row 2; contract_modified, never a fix run
3policy-bot: main is green, posted by policy-botpolicy_absent, policy_pending, gate_path
4The author is the agents’ App. With approve-to-merge, a maintainer’s approval of the current commit merges herenot_agent_authored, approval_stale, changes_requested
5The class is live or shadowclass_unarmed
6The reviewer approved, the room reported the commit, every approval is of that commitno_approving_verdict, head_unreported, verdict_stale
7The commit’s trailer names one of the task’s runsforeign_trailer
8The class’s limits: allowed and protected files, modify-only, files and lines, diff shape, holdout checks, oraclefile_not_allowed, file_protected, file_added_or_deleted, patch_too_large, class_mismatch, holdout_red, oracle_finding_open, oracle_unrequested
9A live class: the breaker has not paused itclass_paused
10A live class: under merge.autoMergesPerDay and the class’s maxPerDayauto_merge_cap
11The class is liveshadow_would_arm: the decision is posted, nothing merges
12Every push to the branch was the agents’ App’shuman_commit_in_pr

A shadow class skips rows 9 and 10: it records shadow_would_arm whatever the breaker and the day’s count say, and its record notes what they read. Row 8 reads GitHub’s own file list, and only once every other row would merge. Maku’s merge gate documents each reason’s next step.

Settings

Example (the reference host), from tooling/base/agent-factory/helm-values-configmap.yaml:

Settingcloud-native-refai-native-ref
requiredChecksThe 4 contexts of main’s protectionvalidate, lint
autoMergesPerDay12
fixRuns21
verifyFor · revertWindow30 min · 168 h30 min · 168 h
approveToMergetruetrue
breaker{window: 10, maxReverts: 1, maxFollowUps: 1, recoverAfter: 3}The same

A class can set a lower maxPerDay of its own. Every other key: harness reference.

Hand-off reasons

The common reasons and your move are on the hand-off. A hand-off goes back to the gate by itself when its cause clears:

ReturnWhen
reentered_approvedA maintainer approved the commit it was handed off at
reentered_day_turnedAn auto_merge_cap hand-off, on the next UTC day
reentered_unpausedA class_paused hand-off, once the breaker lifts the pause
reentered_policypolicy-bot now passes the commit

No approval returns these nine. Fix the pull request, or read it and use the bypass: not_agent_authored, gate_path, contract_modified, not_owned, steering_path, foreign_trailer, not_mergeable, changes_requested, human_commit_in_pr. One exception: a changes_requested hand-off returns on an approval once no maintainer’s newest review still requests changes.

Run endings by role

What each role starts on and how its run can end. The switches are per class, under loops.<class> and repositories[].classes.<class>.

RoleStarts whenEnds with
Scoperloops.<class>.scope: scoper; the task’s first runready starts the implementer; question labels the issue factory/needs-author; split_proposed. Stopped at 40 tool calls (budget_calls)
Splitterloops.<class>.split, after a maintainer’s /factory split2 to maxChildren parts; the parent ends children_merged, child_dropped or budget-family
Testerclasses.<class>.testFirst: true (needs checks: true)contract_ready, not_testable, contract_not_red, contract_outside_tests; escalates contract_invalid, contract_disputed
ImplementerAlways; again on Request changes, red CI, /factory retry or a resumeOpens the pull request; judge_impossible, stalled, not_met, deadline, budget-run, agent_error, stuck, contract_modified
Completion judgeAn implementer run that finishes, on a class with a loop rowmet; not_yet continues the run, at most continuations (2) times; impossible escalates
ReviewerThe task’s template (pair, trio)Approve sends the task to CI; changes start a revision, at most maxReviewRounds (2); review_rounds_exhausted, no_verdict
TriagerA RunLore findingproposal_ready, published by a maintainer’s factory/publish; or no_action

A failing named check is never met. A run with no scoper handoff runs the implementer unscoped.

The decision record

Every decision that is not a wait appends one hidden line to its comment, with what each row read. Hand-offs post it on the issue; shadow decisions post it on the pull request. An example record:

<!-- agent-factory-decision head=67dfa768 class=docs decision=human:class_unarmed files=-/0 lines=-/0 shape=- holdout=- oracle=- age=- evidence=- paused=- capped=- reviewer=glm-5.3 -->
FieldReads
decision=<verdict>:<reason>
files=, lines=GitHub’s counts over the class’s caps (0 is no cap)
shape=, holdout=, oracle=, age=, evidence=ok, n/a when the class declares none, or the refusal’s reason
paused=, capped=What the breaker and the day’s count said
reviewer=The reviewer’s served models; none without a reviewer

- means unread, never 0: a refusal leaves the rows after it unread. merge_gate_off, ledger_truncated and the waits’ own endings (ci_cancelled, ci_wait_expired) carry no record. To count and replay records, see run auto-merge safely.