Pull request follow-up
What agent-board poll does today, and every option of the caller's
agent-board.json it reads. Pass runs this as its stage 2;
poll stays a separate command only until the cutover, when it becomes that
stage (architecture). Runs it starts go through
the caller's dispatch workflow (dispatch).
One pass
agent-board poll follows the bot's pull requests, one pass a run:
it prints the operator's and the bot's activity from the events of every
repository on the board (given by --owner/--project or --snapshot) and of
--watch/--follow, a step for each of the bot's open pull requests in the
--follow repositories (keyed on the latest submitted review of the head by the
operator, or else, with --review-workflow, by the review job as
github-actions[bot], matched by its fixed user id; not a status and a VERDICT
comment), and when the next pass is due
(five minutes while anything changed or a pull request is in flight, doubling
while idle, at most six hours apart). GETs are conditional against a disposable
state file; the pull request query is GraphQL and is not. --dry-run takes no
step. --apply takes its steps with the bot's GH_TOKEN (checked to be
--bot's, as only the bot's markers and claims are read back):
merge an approved green head (pinned to the head that was read), enqueuing it
when the base branch's rules require a merge queue, otherwise using an allowed
repository merge method (rebase, squash, then merge in preference order).
Queue admission also uses expectedHeadOid; GitHub owns revalidation after
admission. Already queued and auto-merge-enabled requests are skipped, never
enabled as a fallback. Label needs-human with the marker comment, and start a fix run
through the caller's --dispatch-workflow with DISPATCH_TOKEN, at most three
a pass. A pass claims each fix round with a
comment naming the marker comment it decided from, and only the earliest claim on
that comment, while that marker is still the newest, starts a run. The winner
writes the round's marker over its claim, so a round is one comment. A claim
that lost is kept: the read-back pages by number, so a deletion while another
pass reads would shift a page past the claim it must see. Markers are otherwise only added: each
needs-human stop is a new comment, and the one later write is a round's run
id, into its own comment. A round's comment is a person's to keep: deleting it
leaves the marker it was claimed from as the newest, and the round is counted
again. Readers use the newest round or stop marker by creation id, so a
late result cannot roll back a newer round or stop, and each removal of the
label is claimed from a stop of its own. Queue-only markers carry queue_only:
their newer queue state is read separately, without replacing the round's
identity or accounting, including while its winning claim is being patched.
A queue write after a reset records queue_reset_from, clearing only that
stop's state without changing the identity a concurrent reset claim names.
A stop is added after its label, and
--apply reads the comments before the label and without the response cache,
so a stop another pass is still writing is not taken for a reset. Steps are taken in --follow
repositories only, besides the runs operatorComments starts (below); the board widens what is read, not what is changed. The
workflow, its bounds and the tokens stay in the caller's repository. Promoting operator-approved fork pull requests upstream is not
implemented. Every workflow of a repository reviews as the Actions bot, and a
branch of the repository could add one that approves its own pull request, so
the bot's review counts only where the pull request's own workflows cannot
review (the bot's pull request from its fork into a public repository, on
any branch of the fork, dispatch/ or another such as the release train's,
which also get fix and rebase rounds (agent-board#142); its workflows get a
read-only token) and only where the run its marker names
is a pull_request_target run of --review-workflow for that head that ran
while the review was submitted, which keeps out the repository's other
workflows. Anyone who can push a branch to the repository (or change what the
review workflow calls) can still produce a verdict that counts: a push workflow
can copy the marker while the job runs, and a branch can be the base of a pull
request with the same head. Without the flag, and elsewhere,
only the operator's verdict counts; a review commit status is not a verdict.
The operator's verdict of the head outranks the job's either way. A pull
request labelled needs-operator-approval (any case), or that closes an issue
so labelled, is not merged or re-queued on the job's approval: it waits for the
operator's own approval of its head (agent-board#293). The label is read from
the pull request and the issues it closes, so anyone with triage access to
either can set it; the operator creates it in each followed repository. The
bot's token can remove labels too, so it guards against a merge by default,
not against a hostile bot. Activity is
printed, not yet handed to a controller pass, and
the events API serves one page of 100 a pass, so a busier gap is reported as
such. A pull request with more labels, reviews or commits than its one query
reads, or that closes more than 10 issues or one with more than 50 labels,
waits rather than being decided on part of them; its head's checks are read on
for up to 5 pages of 100, and past them, or if the head moves or the count changes between pages, it waits too. Of a check that ran more than once
(by the same app, workflow and event), only the newest run counts. Required checks come from the base branch's rulesets and its classic
protection; a required check that never reported is pending, and a protected
branch whose requirements cannot be read is an error. Where the base has no
merge queue, the poll's own merge is the last gate, so every check on the head,
required or not, must have passed, been skipped or be neutral, but for an optional
review status (agent-board#163); a failed optional check gets fix rounds as a
required one does. It has been exercised against recorded responses only.
Configuration
poll --config PATH reads the caller's repository-owned agent-board.json:
operator, bot, dispatchRepo, follow, dispatchWorkflow, reviewWorkflow, fixPools, and
optional mergeAuthors, toilFixPool, conflictFixPool, kindPools, mergeQueue, continuation, diagnosticPool, operatorComments, notifications, brokerOutage, operatorAnswers, rebaseRounds, pace, lockCollaboratorItems, repositoriesPerPass, generatedBy. File settings override the
corresponding flags. Every reader of the file (poll, supervisor, ask,
--board-config, reconcile plan --config) refuses a top-level key that is none
of these and none of the other commands' sections (board, statusReconcile,
epics, decisions, schedule) or the caller's scheduler's (coordinator,
source, branch, which agent-board ignores): every option ships off, so a
misspelt one would otherwise leave its feature off silently. The supervisor's
agent object likewise refuses unknown keys, and agent.kinds a kind it
cannot dispatch. The
workflow must check this file out from its trusted
default branch, never a dispatched head or dispatcher-controlled workflow input.
The board and watch still only widen activity reads. No account or repository
is built into these settings. mergeAuthors defaults to empty; these authors
are merge-only, cannot start fix or continuation runs, read fix markers/runs,
or receive needs-human, even on harness-shaped branches. Existing review
provenance requirements are unchanged: outside the public bot-fork harness,
only the operator's head approval counts.
Unfollowed authors are reported as skips, including when the pull request query
disagrees with the open-list author; they do not count as pass errors. API and
repository failures still count as errors, without stopping other repositories.
An absent/empty toilFixPool leaves normal fix-round pool selection unchanged.
When set, only a first round with no failed check and either no rejected review
or wholly toil findings uses it. Review classification conservatively accepts
simple top-level formatting, rebase and message findings, rejecting ambiguous
prose, severe findings, code references, stale or cut reviews and conflicts
alongside rejected reviews. This is cost routing under the same fix bounds,
not an authorization decision. The pool is recorded in the marker so the next
round after toil uses fixPools[0]. An optional conflictFixPool takes such
a round, and a rebase round, where the pull request conflicts with its base;
absent/empty, those stay on toilFixPool. kindPools.fix supplies a pool only when
the fix-round list is empty; other kinds are not dispatched by poll.
pace (absent by default, which leaves pool selection as above) names the
usage snapshot the supervisor's pace check reads (repository, issue, bot)
and an optional marginPercent (0 to 100, default 0). With it, a fix round
takes the fixPools pool with the most room under its ceiling instead of the
next in turn, and a round, triage or continuation whose single configured pool
is held waits; so does every run with a configured pool while the snapshot is
missing or older than 45 minutes. A pool is held when its snapshot entry says
hold: true (shown until its next_dispatch_at), whatever the reason, or gives
a hold that is not a boolean; its room is allowed_percent + burst - used_percent. Only an entry without hold, from an older snapshot, is held
here when its share used exceeds the share of its window passed by more than
the margin, with room the share passed less the share used. A pool the
snapshot gives no current window for, or does not name, is unpaced: usable, but never
chosen over a paced pool with room, and ties go to the round's usual turn. Each pass ends
with a pools: line naming every pool's mode.
pace.overrides (absent by default) changes one pool's pacing for a while
from the committed file rather than by editing the margin and pool lists and
remembering to revert (agent-board#235). Each entry has a pool, a required
until (RFC 3339, at most 7 days ahead), a mode and an optional one-line
reason. In force, spend means the pool is not held by the poll's own pacing
and is chosen over any pool without an override; a used-up window still holds
it (indefinitely where its reset is absent or unreadable), and so does missing
usage, since an override lifts pacing, not caps. hold holds the pool
until until whatever its pace. An entry past its until is ignored and named
in the pools: line (override for claude expired at …), so a forgotten one
shows rather than pins behavior. A missing or past-the-bound until, an unknown
mode or field, a pool named twice, or a pool no setting names (each tier as
its pools) refuses the poll's configuration. The pools: line names each
override in force with its reason, and the supervisor's pace check shows it
as evidence, warning rather than failing for that pool. The snapshot's own
hold (agent-board#233) does not say why it holds, and a pool over its pace
may be out of its session too, so any hold but false still holds a spend
pool until the snapshot reports pace and cap reasons apart.
Pools have tiers (agent-board#228): fixPools and each kindPools entry may
name a tier, tier-1 or tier-2, instead of a pool, so that tier-1 work
spreads over claude and openai rather than going to one hardcoded pool. poll --pools PATH reads the caller's repository-owned pools file (from the same
trusted checkout), where each pool has its tier
(1 to 9) and "auto": false keeps a hand-dispatch pool out of its tier. A
tier stands for its automatic pools in name order: with pace, the run takes
the one furthest under its pace, skipping held ones and waiting with the usual
message when all are held; without it, fix rounds take the tier's pools in
turn as listed fixPools do, and a kindPools run takes the tier's first pool. The dispatched run's pool input and its marker name the pool
chosen, never the tier. A tier the file does not give, a tier with no automatic
pool, a tier named without --pools, or a tier in toilFixPool,
conflictFixPool, diagnosticPool or continuation.pools (which name the
one pool a round must take) refuses the configuration. A configuration that
names no tier needs no pools file and behaves as before. The supervisor's
executor reads its kind's tier from the pools file it already takes, and admits
a request for any automatic pool of it.
poll --fix-bounds PATH (and pass --fix-bounds PATH) reads the caller's
agentic-job bounds file for its fix kind, tracker's
.github/agentic-job/fix.toml, from the same trusted checkout, never from a
dispatched head or a workflow input (agent-board#264). Every round the poll
starts is dispatched as that kind: fix, rebase (a conflict's included),
diagnostic, continuation and answer rounds, and a fix round on the operator's
comments. A pull request whose base repository is not in the file's repos,
or whose head branch is not in its outputs.push_to_pull_request_branch.branches,
gets none of them: no claim, marker, label or dispatch (only an earlier
answer round's interrupted stop is still finished), and the line says
skip: left to the operator: head branch "ci-drop-grub-cc" is outside the fix bounds
(or repository OWNER/REPO is outside …). It is not counted in flight. On the
operator's comments the thread's line says the same and the comments are
handled, as for a left to a person thread. Merges and every other step are
unchanged. Only those two keys are read; the rest is agentic-job's. A glob is
read only as OWNER/* in repos (owner and name compare case-insensitively)
and PREFIX/** in branches (any branch starting PREFIX/), and any other
*, ?, [ or {, a missing key or a value that is not an array of strings
refuses the file. This only saves the run the policy step would refuse; it
reads the bounds file and does not replace that step, which stays the
boundary. Without the flag nothing is checked, as before.
diagnosticPool (absent by default, which labels a pull request needs-human
once its 2 fix rounds are used) gives a pull request that still fails one more
round, the diagnostic round, on that pool, before needs-human
(tracker#649). It is counted as fix round 3 in the marker, claimed and recorded
like any round, and given once. Its task also carries the first failed check
that is an Actions job of the repository, the step it failed at, and the last
2000 characters of the first 50 failure annotations of its check run, besides
the review's text as every fix task does; the merge queue's failure, where that
is the one, is its own excerpt. These are annotations, not the log, which the
API serves only from a redirect off the API, so for a plain test failure they
may say little more than the exit code. A failure after it goes to
needs-human, and the stop comment quotes the same excerpt in a fenced block,
its backticks and <!-- defused.
Setting it lets the poll start three runs for a pull request instead of two.
rebaseRounds (0 to 3; absent or 0 by default, which charges a conflict to the
fix rounds as any failure) gives a pull request of the bot's whose head the
review approved, and whose only failure is a conflict with its base, that many
rebase rounds apart from its fix rounds (agent-board#109): a base that moved
is not the change's failure, and an approved pull request that used its fix
rounds on review changes went to needs-human for a rebase alone. A rebase
round is a fix-kind run whose task asks only for the rebase, counted in the
marker as rebased, claimed, recorded, waited for, refunded after an outage
and stopped like a fix round; its pool is picked as a first fix round's is.
The rebased head needs the review's approval again before it merges, so a
base that moves again before that approval is a fix round. With a
failed check, a requested change or no verdict, or once the rebase rounds
are used, a conflict is a fix round as before. Setting it lets the poll start
up to that many more runs for a pull request; the cap bounds runs on a base
that keeps moving under it or a rebase that never lands.
A head that is approved and green but behind a base without a merge queue
whose rules require it up to date (mergeStateStatus BEHIND) is not merged:
GitHub refuses that merge (agent-board#273). The bot's own pull request is
instead rebased onto its base with updatePullRequestBranch, pinned to the
approved head, at most one per base per pass; the rest on that base wait for a
later pass, and the base moving again means another update. Updates are not
counted against rebaseRounds or any cap but --max-writes. As after a rebase
round, the new head needs the review's (or the operator's) approval again
before it merges. A mergeAuthors pull request that is behind is a skip, left
to its author. Only BEHIND is read: a head GitHub reports with another state,
such as BLOCKED, is merged as before, and a refusal is an error.
Stacked pull requests (job plans, agent-board#133):
the poll merges or queues a pull request only into its repository's default
branch (defaultBranchRef; one not read waits), so an approved, green pull
request stacked on another's branch waits rather than landing on an
unreviewed head. For the bot's pull request on a dispatch/ base, it reads
the bot's pull requests from that branch of the repository itself (at most
10; a fork's branch of the same name is another branch): while one is open it
decides as usual; once the newest has merged, it retargets the upper pull
request to the branch that one merged into, the default branch or a
dispatch/ one, with a PATCH of its base and no round; with the newest
closed unmerged, none at all, or a merge into anything else, it labels it
needs-human. A conflict with the new base is then a rebase round under
rebaseRounds, or a fix round, as any conflict is. A head whose verdict
predates its newest base change (BaseRefChangedEvent, also GitHub's own
retargeting when a merged head branch is deleted), or whose review job run
started before it, is not merged on it: that review read the change against
the branch below, with that branch's review workflow. Nothing asks the review
job to run again, so the bot's pull request goes to needs-human for the
operator to review the head against the new base.
brokerOutage (absent by default, which charges every failed run) keeps a
broker outage from using a round (agent-board#108):
{"repository": "OWNER/REPO", "author": "LOGIN", "leadSeconds": N}, all
required, N 0 to 25200 (the run timeout). Only for a fix or continuation run
the marker names that completed other than success, the pass reads one page
of 30 each of the open and the last two days' closed issues labelled outage
in that repository, and keeps those author opened whose body starts with
<!-- tracker-broker-outage -->, as the broker probe writes them. Each is an outage from N seconds before it was opened until it
was closed. A run whose updated_at falls in an open one waits; in a closed
one, the round is given back in a new marker (fix rounds or continuation
rounds one fewer, no head or run named), so the next pass starts it again
under the usual rules and the run is not given back twice. The refund is
claimed from the marker it gives back, as a round is, so a pass that read the
same run late cannot roll back a round started since. A cap raise that run
used is given back with it. The decision names the issue. A read that fails or
fills a page charges the run as before and says so. A run that succeeded
without a push, a round with no run id that timed out, or a round started on
the operator's comments (which stay handled) is charged during an outage too.
Anyone who can open
issues as author in that repository can give rounds back; one outage can
cost at most one extra run a round, as a run that fails while it is still open
waits for it to close.
mergeQueue (absent by default, which leaves a removed pull request to be
enqueued again like any other) has the poll own the merge queue
(agent-board#91). {"tries": N} (1 to 10, default 3) bounds the re-queues of
one head. For the bot's pull requests from its fork the newest
RemovedFromMergeQueueEvent the marker has not seen is read once:
stack_invalidated, merge_conflict on a head that no longer conflicts, and a
failed_checks or checks_timed_out whose merge-group run is gone or failed in
setup or checkout are re-queued; a job that fails the head's merge-group run
twice at the same step becomes a failed check (only a run created after the head's commit and
the previous removal, and not the run already read, counts; runs are told apart
by id, as a head re-queued onto an unchanged base gets a group on the same
branch), so the head gets a fix round whose task names the
job and step and carries its failure annotations; any other reason, even
where a fix round is due, or a removal past the tries, goes to needs-human
with the reasons. GitHub's beforeCommit of a removal is the merge-group
commit, not the head (agent-board#254), so a removal is attributed by the
poll's own record: every enqueue first records its head in the marker as
queued. A removal whose beforeCommit is the current head, or whose
recorded queued is, is charged to it; when the marker's head has changed,
one recorded for another head whose beforeCommit names another commit is
marked seen and not charged. With no record, as for a head queued by a person
or before enqueues were recorded, or no beforeCommit, missing attribution preserves the stop;
commit dates are not evidence of when a head was queued. Each enqueue, re-queue,
fix and stop records the queue state in the marker, and the decision line says
why. A merge, or a rebase fix round, of any followed pull request waits while
an entry of the base branch's queue changes one of its files, compared by exact
path, or while the files or the queue were not read whole. A job that also
failed for other entries is not read as shared, because the groups behind an
entry carry its change. Re-queuing only goes through the normal merge decision,
pinned to the approved head; the setting adds no write the poll did not
already make.
operatorComments (absent by default, which reads none) picks up the
operator's comments outside the followed repositories (agent-board#104):
{"repositories": ["OWNER/*", "OWNER/REPO"]}, 1 to 10 entries, each an
organization or a repository. Each pass makes one search
(commenter:OPERATOR updated:>=CURSOR in those scopes, oldest update first,
three pages of 20 threads), resuming from a cursor and page in the state file
at the first thread not done with (a resumed search read to its end is read
once more from its start where threads updated meanwhile may have moved one
not yet read before where it resumed), and reads the
comments, reviews and review comments of each thread found. The operator's
comments from the last two days that no round's marker or triage claim of
the bot's names start one run a thread: on an open pull request the bot opened
(upstream or in its fork), a fix round counted in its marker like any other (within
the 2 fix rounds, not while one runs for the head, not once labelled
needs-human), whose task quotes them; on any other issue or pull request,
a triage run whose task quotes them, says the operator wrote them, and says
whether a /bot or @BOT line asks for action (a research run where such a
line asks for research or an investigation). The dispatch workflow takes only
review and fix runs on a pull request, so on someone else's pull request the
run's item is a tracking issue in the dispatch repository, titled
OWNER/REPO#N: operator request, that quotes the comments and links the pull
request (agent-board#106). Only a public repository's pull request gets one, so
nothing private is copied to the tracker; any other is left. The claim on the
pull request records its number and links it once the run is dispatched; later
comments are quoted on the issue the newest claim names, if the bot opened it
for that pull request and it is open, else (also where it is gone) on a new one.
Each is claimed on its
thread first, as a fix round is, so a comment is acted on once whether or not
the cursor survives. A run GitHub refuses (a 4xx) is recorded on its claim or
marker, without using the round, and tried again on later passes, three times
at most. The claim or marker names the comments as pending until a write after
the dispatch records its outcome. One still pending after the run timeout
counts as a refusal, so a refusal whose record could not be written, or a
started run whose record could not, is tried again too, within those tries
and the fix rounds (agent-board#185). A
fix round's claim that no marker followed within the run timeout is
superseded by a marker restating the state. The runs count against the three a pass and go through
--dispatch-workflow and its bounds. Followed repositories are left to the
steps above. Each thread acted on gets a decision line; a preview does not
advance the cursor.
notifications (absent by default, which reads none) reads the bot's GitHub
notifications, which reach no automation otherwise (agent-board#227):
{"reasons": [...], "peers": [{"login": L, "repositories": ["OWNER/REPO"], "kind": "research"}]}.
reasons is 1 or more of assign, author, comment, manual, mention,
review_requested, state_change, subscribed, team_mention; peers is
at most 10, each with 1 to 10 repositories, and neither the operator nor the
bot. After operatorComments, each pass lists the bot's unread threads
(GET /notifications, 20 a page) updated since a cursor in the state file,
reaching back at most the same two days: one page, or where the last pass left
pages, three more before the oldest it listed, so that threads left unread
cannot keep older ones out of reach. Where all of those were updated in one
second, the listing's only position within it is its pages, so the pass reads
on to that second's end (at most ten pages) before moving past it, and else
reads it again next time. A page unchanged since the last pass is revalidated with its
Last-Modified, which costs no rate budget; X-Poll-Interval (60 s) is under
the poll's five-minute floor and is not read. A thread on an issue or pull
request with a named reason, outside the followed repositories, is routed.
The operator's new comments go to the operatorComments handling above, with
its claims, so a comment both find starts one run; outside that setting's
scope only where one of them has a /bot or @BOT line, as anyone can notify
the bot on a thread the operator commented on, and only in a public
repository, as the brief is copied to the dispatch repository. A peer's new comments (and
opening post) on an open issue in that peer's public repositories start one
research run on it, claimed as a triage is, whose brief asks for one
answering comment of facts, links and documentation, no action, no secret or
deployment detail, and quotes the peer as data (with < escaped); after
three runs on a thread (counting each claim not refused, as one written before
its dispatch may have started a run) a peer's next question there is left to a person, so
two bots cannot answer each other without end. Anything else (a peer
elsewhere, on a pull request or a closed issue, anyone else, a review request
with no comment) starts nothing and is a
notification (REASON) from LOGIN: left to a person, URL line in the pass's
output. That line, not a board item, is the record: the notifications of
anyone who can mention the bot would otherwise become writes on the board.
A thread that is gone is left with a line. Threads are marked read after the
last page is read (marking one read moves the later ones up a page), only
once done (its run started, its line printed, or left to the rest of the
poll, so that those do not fill every later page) and only where the thread
was not updated since it was listed. One whose claim or dispatch failed stays
unread, and the cursor stays before it, as it does before one that could not
be marked read (from the budget or an error); so does any other subject (an alert,
an invitation, a release), for a person reading the bot's inbox. The
three-run cap is per thread: a peer opening many issues is bounded only by
the three runs a pass.
The token is the poll's GH_TOKEN: listing notifications needs the bot's
classic token with the notifications (or repo) scope, as fine-grained
tokens and App tokens cannot read them. A preview neither marks a thread read
nor advances the cursor.
lockCollaboratorItems (1 to 100; absent by default, which locks nothing)
is an opt-in left from when supervise execute took only a locked item
(agent-board#139); dispatch no longer needs it (agent-board#155), but a
supervisor close still takes only a locked, collaborator-only issue. Each pass, after its other steps, it
checks up to that many open, unlocked issues of the
followed repositories with the input check dispatch makes, and with
--apply locks those with input only from collaborators (lock_reason
resolved). An item with anyone else's input stays unlocked, with a line
saying why, and waits behind the others until it changes. A line counts the
items locked, left unlocked and not checked. Pull requests are excluded: the supervisor
does not dispatch them, and locking them can prevent job tokens from reviewing.
repositoriesPerPass (1 to 50; absent by default, which takes steps in every
followed repository) bounds one pass's pull request steps to a deterministic
admission envelope over the followed repositories (agent-board#182, after
gh-cao's control precompute): the followed set sorted and deduplicated, a
sha256: digest of it as its version, the first that many repositories from
the cursor the last pass left in the state file, and the repository after
them as the next cursor (none at the end, so the next pass starts over). The
pass prints the envelope before its steps, as
envelope: inventory=sha256:… count=N cap=C admitted=A,B next=R|start, so two
passes' envelopes can be compared; a followed repository outside it gets a
skip: outside this pass's envelope line, its pull requests are not read, and
a step in it is refused. As with operatorComments, a preview keeps the
cursor; an applying pass moves it past the admitted repositories, one whose
step failed included, and a pass without the option drops it. The envelope
bounds the per-repository pull request steps only: activity reads,
operatorComments and lockCollaboratorItems keep their own bounds.
generatedBy (one trimmed line of at most 200 characters; absent by default,
which keeps Generated-by: https://github.com/cgwalters/#llms) is the
attribution the poll's comments and the supervisor executor's writes end
with, and the one the executor strips from the agent's text before signing
it once (agent-board#226).
operatorAnswers (true; absent or false by default, which leaves a
needs-human pull request to a person taking the label off) takes the
operator's comment as the answer the label waits for (agent-board#113). On
a followed pull request of the bot's whose newest marker is the poll's stop,
which still carries the label and whose newest labeled event for it is the
bot's, the operator's comments, reviews and review comments with text, from
the stop on and named by no marker, start fix round 1, with fresh counts as
a person's reset gives: claimed from the stop (the claim a reset's round
makes, so one of them goes on), then the round's marker, which names the
comments, and the run, whose task quotes them; the label comes off last, so
a pass that dies first leaves it on, and only while that bot's labeled
event is still the newest, so a person who puts it back meanwhile keeps
their hold; their labeled event seen after the removal puts it back on. A review comment is dated by its review's submission. Any comment of theirs counts, a "wait"
too: the rounds then go as after a reset, which may merge a head that is
approved and green. The task lets the run close the pull request with a
comment saying where the work goes, where the operator says it belongs
elsewhere; that needs close_pull_request in the dispatch workflow's bounds
for a fix run, which is not this repository's to set. Only operator's
comments count; a label a person put on is theirs. A run that cannot
start, or a claim never recorded, adds a new stop, after which the operator
answers again. Issues are not followed, so a needs-human issue is left as
before.
The poll never takes a pull request out of draft. Since agentic-job v0.5.1
(agentic-job#691) opens complete work ready and only a
checkpoint
as a draft, a draft of the bot's pull requests from its fork whose head commit
message or body holds one gets a continuation: a fix-kind run on its branch
whose task carries the checkpoint, before any fix round (its checks are of
unfinished work). Its decision line reads continue: continuing (round N): its checkpoint says REASON. Continuation rounds are counted in the marker apart
from fix rounds; continuation.rounds (0 to 5, default 2) caps them, past
which, or for a malformed checkpoint, the pull request gets needs-human
with the reason. A draft with no checkpoint also goes to needs-human
instead of being marked ready on green checks. continuation.pools (absent
by default) names a pool for a budget, runtime or cap checkpoint, as
pools are how a run gets more budget, time or a higher cap; the cap pool
is used once a pull request. A continuation that completes is marked ready
by agentic-job, which in v0.5.1 does not rewrite the title or description
from its commits. With continuation.retitle: true (off by default) the poll
does: a ready pull request of the bot still titled or described as partial
work (or with the placeholder title), where the newest round is a
continuation or fix and the head moved since, is approved and has green checks,
gets the head commit's subject as its
title and the rest of its message as its description, with the old
description's closing references (Closes #N) kept. A failure is fixed first,
hence no failing or pending checks authorize this edit. A message that is the
harness's own words, carries a checkpoint, has no body, or does not fit
GitHub's bounds (256 and 65536 characters) is not copied, and the pull request
goes to needs-human as before. The edit cannot be pinned to the head.
statusReconcile (absent by default) previews whole-board Status drift on
every poll:
{ "statusReconcile": { "enabled": true, "project": "https://github.com/orgs/example/projects/1", "repositories": ["example/intake", "example/code"], "runRepositories": ["example/runners"] }}Supply the board with --owner/--project or --snapshot; it must match the
configured project. Each poll prints a status-preview= plan to stderr, even
with poll --apply; the poll never writes Status. The pass's Status stage
(pass) replaces this preview; the two share the
rules' precedence, while the pass reads its own facts (no review request or
linked-field rule) and also releases Done.
reconcile plan --config agent-board.json uses the same rules; --emit lowers
proposals, retaining issue versus PR identity. There are at most 500 additional
read requests per pass; exceeding the bound fails without publishing a partial
status plan. Preview errors are reported on stderr and in the final exit status,
but do not stop the PR poll. Run URLs are only read in runRepositories, so
editable board text cannot send token-bearing reads to arbitrary repositories or
API paths.
The rules inspect every in-scope issue and PR without Lead, Workflow or
assignment exemptions: closed/merged content or a nonempty set of all-merged
linked PRs becomes Done; an open needs-human label (any case) or an outstanding
operator review request becomes Needs human; otherwise a draft PR or active
recorded Run becomes In Progress. A completed recorded Run releases In Progress
or Needs human to Todo. Terminal facts win, then human asks, then active work.
Missing run data is unknown, not proof that no live job exists. Linked PR facts
come from complete native PR-valued project fields. Coverage and status schema
checks still block incomplete or ambiguous repairs. This mode maps all closed
content to Done, including NOT_PLANNED; the default rules keep Cancelled.
Closure with a reason, Draft-item actions, complete live-job absence detection,
epic classification, Priority defaults and off-board inventory are not
inferred.
Organization status
agent-board status --owner ORG --config agent-board.json [--job-limit N]
reports how loaded the organization is (agent-board#262), read-only through the
supervisor's bounded API (at most REQUEST_LIMIT requests and
TIMEOUT_SECONDS, no write). Runner load is the queued and running jobs of
every unarchived repository's queued and in-progress runs, by runner label
(self-hosted as one), with the configured dispatchRepo's dispatchWorkflow
runs counted as agent runs apart from other CI, against --job-limit.
Agent runs are counted by the kind and pool their titles name, with the last
hour's outcomes; pools, where the config has pace, are the poll's pace line
from the same usage snapshot; queues are each follow repository's merge queue
and its approved pull requests not in it, among the newest hundred (marked
partial past that); --project adds the board's Status counts and open P0
items with no Run, Result or linked pull request, read by the board's own
bounded snapshot. A section that cannot be read is shown as unavailable and the
command exits 1. The requests grow with the load (one per active run), and are
made one at a time.