From 9840c2a860227a7058b70318dd0c7adfcc153f79 Mon Sep 17 00:00:00 2001 From: Felix Faerber Date: Sat, 4 Jul 2026 23:18:00 +0300 Subject: [PATCH] autopilot(qa): bounce back to the dev instead of halting; stop after 3 bounces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per maintainer: @qa must never change code — it only accepts a PR or sends it back to the dev with precise instructions. Replaces the halt-on-any-problem behavior with a bounce loop: - QA prompt: emit MERGE_PR (approve), or `BOUNCE: @` after spelling out exactly what to change. HALT_AUTOPILOT is now only for problems a dev can't fix (needs a human decision). - publish.sh: on BOUNCE, re-trigger the named dev (via AGENT_TOKEN) with "autopilot fix attempt N/3". Count prior attempts from the PR thread; at 3 it stops (removes the label) and hands to @ffaerber. - publish.sh: after a dev pushes a fix (resume), auto-re-trigger @qa to re-verify — closing the loop (dev fix -> qa re-check -> merge or bounce again). - Updated the initial @qa trigger wording (bounce, not halt). Verified: BOUNCE target parsing and the 3-strike counter (attempts 1/3,2/3,3/3 then HALT). Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/scripts/publish.sh | 43 +++++++++++++++++++++++++-- .gitea/workflows/scripts/run-agent.sh | 25 +++++++++------- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/.gitea/workflows/scripts/publish.sh b/.gitea/workflows/scripts/publish.sh index 2fc4db7..fb2b265 100755 --- a/.gitea/workflows/scripts/publish.sh +++ b/.gitea/workflows/scripts/publish.sh @@ -42,6 +42,7 @@ reply=$(awk ' /^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next} /^[[:space:]]*MERGE_PR[[:space:]]*$/{next} /^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$/{next} + /^[[:space:]]*BOUNCE:[[:space:]]*@/{next} s{ if(/^[[:space:]]*END_SUBTASKS/){s=0}; next } p{ if(/^[[:space:]]*END_PR_DESCRIPTION/){p=0}; next } {print} @@ -137,10 +138,36 @@ if [ "$MODE" != "pr" ]; then ;; esac fi + elif grep -qiE '^[[:space:]]*BOUNCE:[[:space:]]*@(junior|senior|lead)' /tmp/agent_out.md; then + # @qa wants the dev to fix something. Send it back — never fix it ourselves. After 3 bounces, + # stop and hand to the human. QA's feedback is already posted (the reply comment above). + if [ -z "$IS_PR" ]; then + echo "BOUNCE marker but this run is not on a PR thread — skipping" + else + target=$(grep -oiE 'BOUNCE:[[:space:]]*@(junior|senior|lead)' /tmp/agent_out.md | head -1 \ + | grep -oiE '(junior|senior|lead)' | tr '[:upper:]' '[:lower:]') + [ -z "$target" ] && target=$(curl -sS "${hdr[@]}" "$API/pulls/$NUM" | jq -r '.user.login // "junior"') + # Count how many times this PR has already been bounced (marker in the trigger comment). + prior=$(curl -sS "${hdr[@]}" "$API/issues/$NUM/comments?limit=100" \ + | jq -r 'if type=="array" then [.[]|select(.body|test("autopilot fix attempt"))]|length else 0 end' 2>/dev/null) + prior=${prior:-0} + if [ "$prior" -ge 3 ]; then + echo "@qa autopilot: 3 bounces already — halting" + del_autopilot_label "${ISSNUM:-$NUM}" + post "$(printf '🤖 **@qa** — 🛑 still not right after 3 fix attempts. Stopping autopilot (removed the `autopilot` label). @ffaerber please take over — details in the comments above.')" + else + n=$((prior + 1)) + echo "@qa autopilot: bounce $n/3 -> @$target" + curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \ + "$API/issues/$NUM/comments" \ + -d "$(jq -nc --arg b "@$target please address @qa's feedback above and update this PR (autopilot fix attempt $n/3)." '{body:$b}')" \ + -w '\nbounce -> HTTP %{http_code}\n' || true + fi + fi elif grep -qiE '^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$' /tmp/agent_out.md; then echo "@qa autopilot: HALT — removing 'autopilot' label from #${ISSNUM:-$NUM}" del_autopilot_label "${ISSNUM:-$NUM}" - post "$(printf '🤖 **@qa** — 🛑 found a problem, so I did NOT merge. Removed the `autopilot` label (back to human control). @ffaerber please decide next steps (details above).')" + post "$(printf '🤖 **@qa** — 🛑 this needs a human decision (not a dev fix). Removed the `autopilot` label (back to human control). @ffaerber please decide next steps (details above).')" fi exit 0 fi @@ -247,11 +274,23 @@ if [ "$NEW" = "true" ]; then echo "autopilot: auto-triggering @qa to review PR #$prnum" curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \ "$API/issues/$prnum/comments" \ - -d "$(jq -nc --arg b "@qa please verify this PR (autopilot: issue #$NUM is labeled autopilot). Merge it if correct, or halt and remove the label if you find a problem." '{body:$b}')" \ + -d "$(jq -nc --arg b "@qa please verify this PR (autopilot: issue #$NUM is labeled autopilot). Merge it if correct, or bounce it back to the dev with exactly what needs fixing." '{body:$b}')" \ -w '\ntrigger-qa -> HTTP %{http_code}\n' || true fi else # Resume: just link the PR — its body and the diff already carry the description, so we don't # repeat the full write-up in the comment (the reasoning trail below shows what this run did). prpost "$prnum" "$(printf '🤖 **@%s** — pushed an update to the PR:\n- %s%s' "$NAME" "$url" "$activity")" + # AUTOPILOT: after a dev pushes a fix (e.g. following a @qa bounce), hand back to @qa to re-verify. + if [ "$AUTOPILOT" = "true" ] && [ -n "$AGENT_TOKEN" ] && [ -n "$prnum" ]; then + case "$NAME" in + junior|senior|lead) + echo "autopilot: dev pushed a fix — re-triggering @qa to re-verify PR #$prnum" + curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \ + "$API/issues/$prnum/comments" \ + -d "$(jq -nc --arg b "@qa please re-verify this PR (autopilot). Merge it if now correct, or bounce it back with exactly what still needs fixing." '{body:$b}')" \ + -w '\ntrigger-qa -> HTTP %{http_code}\n' || true + ;; + esac + fi fi diff --git a/.gitea/workflows/scripts/run-agent.sh b/.gitea/workflows/scripts/run-agent.sh index 3903df4..527bc31 100755 --- a/.gitea/workflows/scripts/run-agent.sh +++ b/.gitea/workflows/scripts/run-agent.sh @@ -71,17 +71,20 @@ if [ "$MODE" = "comment" ]; then a human does that." if [ "$AUTOPILOT" = "true" ]; then ACTION="$ACTION - AUTOPILOT MODE IS ACTIVE (this issue/PR carries the 'autopilot' label). This grants you a - NARROW, one-time merge authority for THIS PR only: - - If, after verifying, the PR is correct and any CI checks are green, end your reply with EXACTLY - one line: 'MERGE_PR'. The automation will then merge the PR and close the linked issue for you. - Do NOT merge via any other means; only the MERGE_PR marker triggers the merge. - - If you find ANY bug, doubt, or the change is not clearly correct, do NOT merge. Instead describe - the problem clearly and end your reply with EXACTLY one line: 'HALT_AUTOPILOT'. The automation - removes the 'autopilot' label (returning this issue to normal human control) and leaves - it for @ffaerber to decide next steps. Never auto-bounce back to a dev. - Emit AT MOST one of MERGE_PR or HALT_AUTOPILOT, and only after you have actually verified. When in - doubt, prefer HALT_AUTOPILOT." + AUTOPILOT MODE IS ACTIVE (this issue/PR carries the 'autopilot' label). You are the quality gate. + You do NOT edit code or fix anything yourself — you either accept the PR or send it back to the dev + with precise instructions. After actually verifying, end your reply with EXACTLY one of: + - 'MERGE_PR' — the change is correct and any CI is green. The automation merges the PR and closes + the linked issue. Do NOT merge by any other means; only this marker triggers the merge. + - 'BOUNCE: @' — something needs changing. FIRST spell out, specifically and actionably, exactly + what the dev must change (name the file, label, value, hostname, etc.), THEN end with the BOUNCE + line naming who should fix it (@junior / @senior / @lead — usually whoever built it; @senior or + @lead for something harder). The automation sends the PR back to that dev and then re-verifies + with you. After 3 bounces it stops automatically and hands to @ffaerber — so make each round + count and list ALL problems at once, not one at a time. + Use BOUNCE for anything a dev can fix. Only use 'HALT_AUTOPILOT' when the problem is NOT fixable by + a dev — the request itself is ambiguous or needs a human decision — to hand back to @ffaerber. + Emit AT MOST one of MERGE_PR / BOUNCE / HALT_AUTOPILOT, and only after you have actually verified." fi fi else -- 2.54.0