Skip to content

Commit 391371c

Browse files
authored
Merge pull request #42 from githubnext/copilot/fix-gate-iteration-acceptance
Spec a CI-green gate with bounded fix-and-retry loop for iteration acceptance
2 parents b857897 + 83b791f commit 391371c

1 file changed

Lines changed: 60 additions & 7 deletions

File tree

workflows/autoloop.md

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -362,24 +362,76 @@ Each run executes **one iteration for the single selected program**:
362362

363363
### Step 5: Accept or Reject
364364

365-
**If the metric improved** (or this is the first run establishing a baseline):
365+
The sandbox-computed metric is necessary but **not sufficient** for acceptance. The agent's sandbox cannot reliably install many project toolchains (e.g., `bun`, `tsc`, `cargo`, `go`, `pytest`) due to network restrictions on asset hosts, so a "metric improved" signal from the sandbox can mask broken commits (e.g., type-check or test failures the sandbox couldn't observe). Acceptance must therefore be gated on **CI green** for the pushed HEAD commit. If CI fails, attempt to fix-and-retry within the same iteration rather than reverting — reverting throws away mostly-correct work and creates `commit→revert→commit` churn on the branch.
366+
367+
The accept path is split into three sub-steps: **5a (push and wait for CI)**, **5b (fix loop)**, **5c (accept)**.
368+
369+
**If the metric did not improve**, jump straight to the "metric did not improve" path below — no push, no CI gate.
370+
371+
#### Step 5a: Push and wait for CI
372+
373+
**Only entered if the metric improved** (or this is the first run establishing a baseline).
374+
366375
1. Commit the changes to the long-running branch `autoloop/{program-name}` with a commit message referencing the actions run:
367376
- Commit message subject line: `[Autoloop: {program-name}] Iteration <N>: <short description>`
368377
- Commit message body (after a blank line): `Run: {run_url}` referencing the GitHub Actions run URL.
369378
2. Push the commit to the long-running branch.
370-
3. If a draft PR does not already exist for this branch, create one:
379+
3. If a draft PR does not already exist for this branch, create it now (see Step 5c for the title/body format). The PR is needed so that CI runs and so `gh pr checks` has a target.
380+
4. Wait for CI on the new HEAD and reduce all check-runs to a single status — `success`, `failure`, or `pending`:
381+
382+
```bash
383+
PR=${EXISTING_PR:-$(gh pr list --head autoloop/{program-name} --json number -q '.[0].number')}
384+
gh pr checks "$PR" --watch --interval 30 || true
385+
status=$(gh pr checks "$PR" --json conclusion,state -q '.[] | (.conclusion // .state // "")' \
386+
| awk '
387+
BEGIN { r = "success" }
388+
/^(FAILURE|CANCELLED|TIMED_OUT|ACTION_REQUIRED|STARTUP_FAILURE|STALE)$/ { r = "failure" }
389+
/^(PENDING|QUEUED|IN_PROGRESS|WAITING|REQUESTED)$/ { if (r == "success") r = "pending" }
390+
END { print r }')
391+
```
392+
393+
Three outcomes: `success`, `failure`, or `pending`. `pending` should be rare given `--watch`, but the awk fallback is defensive — never accept on `pending`. Treat `pending` as a non-terminal state: re-run the `gh pr checks --watch` step (it does not consume a fix attempt and the per-attempt `--watch` time still counts toward the 60-min wall-clock cap from Step 5b). If `pending` persists past the wall-clock cap, fall through to the `ci-timeout` handling in Step 5b.7.
394+
395+
5. If `status == "success"`, proceed to **Step 5c**. If `status == "failure"`, proceed to **Step 5b**. If `status == "pending"`, re-run this step (subject to the wall-clock cap defined in Step 5b.7).
396+
397+
#### Step 5b: Fix loop (up to 5 attempts per iteration)
398+
399+
If `status == "failure"`, **fix and retry — do not revert, do not accept**:
400+
401+
1. **Fetch the failing check-run logs** for the pushed SHA via `gh run view --log` or the Checks API.
402+
2. **Extract a structured failure summary**:
403+
- Failing job names and the first error line for each.
404+
- **A failure signature** — a stable, normalized fingerprint of the failures (e.g., sorted failing-test names + the top error code, like `TS2339:fromArrays:tests/stats/eval_query.test.ts`). The signature is what the no-progress guard compares.
405+
406+
*(The shared failure-signature extractor lives in the scheduler helper module — see issue #34 for the implementation.)*
407+
3. **No-progress guard**: if this attempt's failure signature exactly matches the previous attempt's signature, **stop**. The agent is stuck in a repeat-loop. Set `paused: true` on the state file with `pause_reason: "stuck in CI fix loop: <signature>"`, append `"ci-fix-exhausted"` to `recent_statuses`, comment on the program issue with the signature and the three most recent attempts, and end the iteration.
408+
4. **Attempt the fix**: feed the structured failure summary back to the agent as the next sub-task (e.g., "CI failed on `<sha>`. Here are the failures: `<…>`. Fix them and push again."). The agent commits the fix and pushes.
409+
5. **Loop back to Step 5a** with the new HEAD.
410+
6. **Budget: 5 fix attempts per iteration.** If the 5th attempt still leaves CI red, set `paused: true` with `pause_reason: "ci-fix-exhausted: <signature>"`, append `"ci-fix-exhausted"` to `recent_statuses`, comment on the program issue, and end the iteration.
411+
7. **Wall-clock cap: 60 min per iteration** including all CI waits across attempts. If exceeded mid-fix, set `paused: true` with `pause_reason: "ci-timeout"`, append `"ci-fix-exhausted"` to `recent_statuses`, leave the current branch state in place, and end the iteration.
412+
413+
#### Step 5c: Accept
414+
415+
**Only entered when `status == "success"`** from Step 5a (possibly after one or more fix attempts in Step 5b).
416+
417+
1. The commit(s) are already on the long-running branch (pushed in Step 5a / 5b). No further pushing needed.
418+
2. If a draft PR does not already exist for this branch, create one:
371419
- Title: `[Autoloop: {program-name}]`
372420
- Body includes: a summary of the program goal, link to the program issue, the current best metric, and AI disclosure: `🤖 *This PR is maintained by Autoloop. Each accepted iteration adds a commit to this branch.*`
373-
If a draft PR already exists, update the PR body with the latest metric and a summary of the most recent accepted iteration. Add a comment to the PR summarizing the iteration: what changed, old metric, new metric, improvement delta, and a link to the actions run.
421+
If a draft PR already exists, update the PR body with the latest metric and a summary of the most recent accepted iteration. Add a comment to the PR summarizing the iteration: what changed, old metric, new metric, improvement delta, the **fix-attempt count** if `> 0`, and a link to the actions run.
374422
4. Ensure the program issue exists (see [Program Issue](#program-issue) below) — for file-based programs that have no program issue yet (`selected_issue` is null in `/tmp/gh-aw/autoloop.json`), create one and record its number in the state file's `Issue` field.
375423
5. Update the state file `{program-name}.md` in the repo-memory folder:
376424
- Update the **⚙️ Machine State** table: reset `consecutive_errors` to 0, set `best_metric`, increment `iteration_count`, set `last_run` to current UTC timestamp, append `"accepted"` to `recent_statuses` (keep last 10), set `paused` to false.
377-
- Prepend an entry to **📊 Iteration History** (newest first) with status ✅, metric, PR link, and a one-line summary of what changed and why it worked.
425+
- Prepend an entry to **📊 Iteration History** (newest first) with status ✅, metric, PR link, the fix-attempt count if `> 0`, and a one-line summary of what changed and why it worked.
378426
- Update **📚 Lessons Learned** if this iteration revealed something new about the problem or what works.
379427
- Update **🔭 Future Directions** if this iteration opened new promising paths.
380-
6. **Update the program issue**: edit the status comment and post a per-iteration comment on the program issue (see [Program Issue](#program-issue)).
428+
6. **Update the program issue**: edit the status comment and post a per-iteration comment on the program issue (see [Program Issue](#program-issue)). Note the fix-attempt count in the per-iteration comment if `> 0`.
381429
7. **Check halting condition** (see [Halting Condition](#halting-condition)): If the program has a `target-metric` in its frontmatter and the new `best_metric` meets or surpasses the target, mark the program as completed.
382430

431+
#### Coordination with PR-health-keeper workflows
432+
433+
If a repo ships a companion PR-health-keeper workflow (e.g., an "Evergreen" workflow that fixes failing CI on open PRs), it should be able to pick up paused Autoloop PRs using the same rules as human-authored PRs. The handoff is via the `pause_reason` field — `ci-fix-exhausted: <signature>`, `stuck in CI fix loop: <signature>`, and `ci-timeout` are all signals that the branch is red and needs an external nudge. Absent such a workflow, the loud pause + structured reason gives a human enough signal to intervene.
434+
383435
**If the metric did not improve**:
384436
1. Discard the code changes (do not commit them to the long-running branch).
385437
2. Update the state file `{program-name}.md` in the repo-memory folder:
@@ -662,11 +714,11 @@ All iterations in reverse chronological order (newest first).
662714
| PR | `#number` or `` | Draft PR number for this program |
663715
| Issue | `#number` or `` | The single program issue (`[Autoloop: {program-name}]`) for this program. Hosts the status comment, per-iteration comments, and human steering comments. |
664716
| Paused | `true` or `false` | Whether the program is paused |
665-
| Pause Reason | text or `` | Why it is paused (if applicable) |
717+
| Pause Reason | text or `` | Why it is paused (if applicable). Common values include `manual`, `consecutive errors`, `ci-fix-exhausted: <signature>` (5 fix attempts didn't fix CI), `stuck in CI fix loop: <signature>` (no-progress guard tripped — same failure signature twice in a row), and `ci-timeout` (60-min wall-clock cap hit). |
666718
| Completed | `true` or `false` | Whether the program has reached its target metric |
667719
| Completed Reason | text or `` | Why it completed (e.g., `target metric 0.95 reached with value 0.97`) |
668720
| Consecutive Errors | integer | Count of consecutive evaluation failures |
669-
| Recent Statuses | comma-separated words | Last 10 outcomes: `accepted`, `rejected`, or `error` |
721+
| Recent Statuses | comma-separated words | Last 10 outcomes: `accepted`, `rejected`, `error`, or `ci-fix-exhausted`. The `ci-fix-exhausted` value is the coarse bucket for *any* iteration that ended because the CI gate could not be made green within the per-iteration budget — including no-progress-guard trips, 5-attempt budget exhaustion, and `ci-timeout`. The fine-grained reason is in `pause_reason`. |
670722

671723
### Iteration History Entry Format
672724

@@ -679,6 +731,7 @@ After each iteration, prepend an entry to the **📊 Iteration History** section
679731
- **Change**: {one-line description of what was tried}
680732
- **Metric**: {value} (previous best: {previous_best}, delta: {+/-delta})
681733
- **Commit**: {short_sha} *(if accepted)*
734+
- **CI fix attempts**: {N} *(omit if 0; only present for accepted iterations that needed fix-and-retry)*
682735
- **Notes**: {one or two sentences on what this iteration revealed}
683736
```
684737

0 commit comments

Comments
 (0)