Skip to content
This text was generated by an LLM (how cgwalters uses LLMs). cgwalters has not reviewed it yet.

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.