Compare commits

..
Author SHA1 Message Date
Felix FaerberandClaude Opus 4.8 cf1e7178b5 fix(autopilot): merge with AGENT_TOKEN so the merge triggers the deploy
@qa's autopilot merge used the built-in Actions token (TOKEN_QA unset -> fell back to GITEA_TOKEN).
Gitea does not fire new workflow runs for pushes made by the built-in Actions token, so the merge
landed on main but deploy.yml never ran — the change was merged-but-not-deployed. Merge with
AGENT_TOKEN (a PAT, already used for delegation and proven to trigger runs) so the push to main
triggers the deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 23:32:12 +03:00
ffaerber 5a9dba64fb Merge pull request 'autopilot(qa): bounce to dev instead of halt; stop after 3' (#51) from fix/autopilot-bounce into main 2026-07-04 22:21:51 +02:00
Felix FaerberandClaude Opus 4.8 9840c2a860 autopilot(qa): bounce back to the dev instead of halting; stop after 3 bounces
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: @<dev>` 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) <noreply@anthropic.com>
2026-07-04 23:18:00 +03:00
ffaerber bb07558d70 Merge pull request '@lead: run without asking user' (#20) from ai/issue-16 into main
Reviewed-on: ffaerber/agents#20
2026-07-04 21:39:01 +02:00
2 changed files with 62 additions and 14 deletions
+48 -3
View File
@@ -42,6 +42,7 @@ reply=$(awk '
/^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next} /^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next}
/^[[:space:]]*MERGE_PR[[:space:]]*$/{next} /^[[:space:]]*MERGE_PR[[:space:]]*$/{next}
/^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$/{next} /^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$/{next}
/^[[:space:]]*BOUNCE:[[:space:]]*@/{next}
s{ if(/^[[:space:]]*END_SUBTASKS/){s=0}; next } s{ if(/^[[:space:]]*END_SUBTASKS/){s=0}; next }
p{ if(/^[[:space:]]*END_PR_DESCRIPTION/){p=0}; next } p{ if(/^[[:space:]]*END_PR_DESCRIPTION/){p=0}; next }
{print} {print}
@@ -119,7 +120,13 @@ if [ "$MODE" != "pr" ]; then
echo "MERGE_PR marker but this run is not on a PR thread — skipping merge" echo "MERGE_PR marker but this run is not on a PR thread — skipping merge"
else else
echo "@qa autopilot: merging PR #$NUM (origin issue #${ISSNUM:-$NUM})" echo "@qa autopilot: merging PR #$NUM (origin issue #${ISSNUM:-$NUM})"
mc=$(curl -sS -o /tmp/merge_resp.txt -w '%{http_code}' -X POST "${hdr[@]}" \ # Merge with AGENT_TOKEN (a PAT) — NOT the built-in Actions token — so the resulting push to
# main TRIGGERS downstream workflows (e.g. deploy). A merge made with the built-in GITEA_TOKEN
# does not fire new runs (loop-prevention), which silently skips the deploy. Fall back to the
# agent's own token only if AGENT_TOKEN isn't set (then the deploy would need a manual run).
mtok="${AGENT_TOKEN:-$TOK}"
mc=$(curl -sS -o /tmp/merge_resp.txt -w '%{http_code}' -X POST \
-H "Authorization: token $mtok" -H "Content-Type: application/json" \
"$API/pulls/$NUM/merge" -d '{"Do":"merge"}') "$API/pulls/$NUM/merge" -d '{"Do":"merge"}')
echo "merge -> HTTP $mc"; cat /tmp/merge_resp.txt 2>/dev/null || true echo "merge -> HTTP $mc"; cat /tmp/merge_resp.txt 2>/dev/null || true
case "$mc" in case "$mc" in
@@ -137,10 +144,36 @@ if [ "$MODE" != "pr" ]; then
;; ;;
esac esac
fi 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 elif grep -qiE '^[[:space:]]*HALT_AUTOPILOT[[:space:]]*$' /tmp/agent_out.md; then
echo "@qa autopilot: HALT — removing 'autopilot' label from #${ISSNUM:-$NUM}" echo "@qa autopilot: HALT — removing 'autopilot' label from #${ISSNUM:-$NUM}"
del_autopilot_label "${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 fi
exit 0 exit 0
fi fi
@@ -247,11 +280,23 @@ if [ "$NEW" = "true" ]; then
echo "autopilot: auto-triggering @qa to review PR #$prnum" echo "autopilot: auto-triggering @qa to review PR #$prnum"
curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \ curl -sS -X POST -H "Authorization: token $AGENT_TOKEN" -H "Content-Type: application/json" \
"$API/issues/$prnum/comments" \ "$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 -w '\ntrigger-qa -> HTTP %{http_code}\n' || true
fi fi
else else
# Resume: just link the PR — its body and the diff already carry the description, so we don't # 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). # 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")" 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 fi
+14 -11
View File
@@ -71,17 +71,20 @@ if [ "$MODE" = "comment" ]; then
a human does that." a human does that."
if [ "$AUTOPILOT" = "true" ]; then if [ "$AUTOPILOT" = "true" ]; then
ACTION="$ACTION ACTION="$ACTION
AUTOPILOT MODE IS ACTIVE (this issue/PR carries the 'autopilot' label). This grants you a AUTOPILOT MODE IS ACTIVE (this issue/PR carries the 'autopilot' label). You are the quality gate.
NARROW, one-time merge authority for THIS PR only: You do NOT edit code or fix anything yourself — you either accept the PR or send it back to the dev
- If, after verifying, the PR is correct and any CI checks are green, end your reply with EXACTLY with precise instructions. After actually verifying, end your reply with EXACTLY one of:
one line: 'MERGE_PR'. The automation will then merge the PR and close the linked issue for you. - 'MERGE_PR' — the change is correct and any CI is green. The automation merges the PR and closes
Do NOT merge via any other means; only the MERGE_PR marker triggers the merge. the linked issue. Do NOT merge by any other means; only this marker triggers the merge.
- If you find ANY bug, doubt, or the change is not clearly correct, do NOT merge. Instead describe - 'BOUNCE: @<dev>' — something needs changing. FIRST spell out, specifically and actionably, exactly
the problem clearly and end your reply with EXACTLY one line: 'HALT_AUTOPILOT'. The automation what the dev must change (name the file, label, value, hostname, etc.), THEN end with the BOUNCE
removes the 'autopilot' label (returning this issue to normal human control) and leaves line naming who should fix it (@junior / @senior / @lead — usually whoever built it; @senior or
it for @ffaerber to decide next steps. Never auto-bounce back to a dev. @lead for something harder). The automation sends the PR back to that dev and then re-verifies
Emit AT MOST one of MERGE_PR or HALT_AUTOPILOT, and only after you have actually verified. When in with you. After 3 bounces it stops automatically and hands to @ffaerber — so make each round
doubt, prefer HALT_AUTOPILOT." 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
fi fi
else else