diff --git a/.gitea/workflows/scripts/publish.sh b/.gitea/workflows/scripts/publish.sh index 5ce5f52..94ea8d6 100755 --- a/.gitea/workflows/scripts/publish.sh +++ b/.gitea/workflows/scripts/publish.sh @@ -60,8 +60,8 @@ del_autopilot_label() { fi } -# drop machine-readable markers: DELEGATE / CLOSE_ISSUE / MERGE_PR / APPROVE / HALT / BOUNCE, and the -# BEGIN_SUBTASKS..END_SUBTASKS and BEGIN_PR_DESCRIPTION..END_PR_DESCRIPTION blocks (the PR +# drop machine-readable markers: DELEGATE / CLOSE_ISSUE / MERGE_PR / RETRO / APPROVE / HALT / BOUNCE, +# and the BEGIN_SUBTASKS..END_SUBTASKS and BEGIN_PR_DESCRIPTION..END_PR_DESCRIPTION blocks (the PR # description is published separately). reply=$(awk ' /^[[:space:]]*BEGIN_SUBTASKS/{s=1} @@ -69,6 +69,7 @@ reply=$(awk ' /^[[:space:]]*DELEGATE:[[:space:]]*@/{next} /^[[:space:]]*CLOSE_ISSUE[[:space:]]*$/{next} /^[[:space:]]*MERGE_PR[[:space:]]*$/{next} + /^[[:space:]]*RETRO[[:space:]]*$/{next} /^[[:space:]]*APPROVE[[:space:]]*$/{next} /^[[:space:]]*HALT([_ ]AUTOPILOT)?[[:space:]]*$/{next} /^[[:space:]]*BOUNCE:[[:space:]]*@/{next} @@ -113,13 +114,18 @@ if [ "$MODE" != "pr" ]; then [ -z "$dev" ] && [ -n "$PRN" ] && dev=$(curl -sS "${hdr[@]}" "$API/pulls/$PRN" | jq -r '.user.login // "junior"') dest="${PRN:-$NUM}" post_to "$dest" "$reply$activity" # recommendations, on the PR - # Bounce budget: count prior "fix attempt" markers on the PR thread; stop after 3. - prior=$(curl -sS "${hdr[@]}" "$API/issues/$dest/comments?limit=100" | jq -r 'if type=="array" then [.[]|select(.body|test("fix attempt"))]|length else 0 end' 2>/dev/null); prior=${prior:-0} + # Bounce budget: count prior bounce TRIGGERS on the PR thread — only @qa-authored comments + # matching the exact "(fix attempt N/3)" template. A loose substring match would also count + # review text QUOTING our own templates (seen on PR #84: the counter jumped 1/3 → 3/3 because + # a qa review quoted publish.sh lines containing the phrase), halving the fix budget. + prior=$(curl -sS "${hdr[@]}" "$API/issues/$dest/comments?limit=100" | jq -r 'if type=="array" then [.[]|select(.user.login=="qa")|select(.body|test("^@[a-z]+ please address my review above and update PR #[0-9?]+ \\(fix attempt [0-9]+/3\\)\\.$"))]|length else 0 end' 2>/dev/null); prior=${prior:-0} if [ "$prior" -ge 3 ]; then [ "$AUTOPILOT" = "true" ] && del_autopilot_label "$ISSN" post_to "$ISSN" "🛑 Still not right after 3 fix attempts on PR #${PRN:-?} — handing to @ffaerber (details on the PR)." else n=$((prior + 1)) + # NOTE: this template and the counter regex above MUST stay in sync — if you reword one, + # reword the other, or the count resets to 0 and the 3-round cap stops working. trig "$dest" "@${dev:-junior} please address my review above and update PR #${PRN:-?} (fix attempt $n/3)." fi elif grep -qiE '^[[:space:]]*HALT([_ ]AUTOPILOT)?[[:space:]]*$' /tmp/agent_out.md; then @@ -203,6 +209,27 @@ if [ "$MODE" != "pr" ]; then exit 0 fi + # --- @pm RETRO: open a retrospective issue for this thread (maintainer asked for a retro) --- + # Creates a retro issue pointing at this issue + its PR and triggers @senior on it (has gitea-api + # to read both threads). The retro produces a LEARNINGS.md PR via the NORMAL choreography (senior → + # pm → qa → merge), and run-agent.sh injects LEARNINGS.md into every future prompt — closing the loop. + if [ "$NAME" = "pm" ] && grep -qiE '^[[:space:]]*RETRO[[:space:]]*$' /tmp/agent_out.md; then + PRN=$(curl -sS "${hdr[@]}" "$API/pulls?state=all&limit=50" \ + | jq -r --arg br "ai/issue-$ISSN" 'if type=="array" then ([.[]|select(.head.ref==$br)] | sort_by(.number) | last | .number // empty) else empty end' 2>/dev/null) + rbody=$(printf 'Retrospective for issue #%s%s.\n\nRead the FULL issue thread%s using the gitea-api skill (issue comments%s and the PR diff). Identify what went wrong, slow, or needed human correction — missed wiring, review misses, bounced rounds, unclear delegation, missing context.\n\nThen APPEND the distilled learnings to `LEARNINGS.md` at the repo root (create it with a short header if missing). Rules for entries:\n- 3 to 6 bullets max, each ONE line: `symptom -> rule for next time`.\n- Concrete and checkable (name the file/step/marker), not generic advice.\n- Do not repeat an existing bullet; refine it instead.\n- Do not rewrite unrelated parts of the file.\n\nThese learnings are injected into every future agent prompt, so quality over quantity.' \ + "$ISSN" "${PRN:+ / PR #$PRN}" "${PRN:+ and PR #$PRN thread}" "${PRN:+, PR comments}") + rnum=$(curl -sS -X POST "${hdr[@]}" "$API/issues" \ + -d "$(jq -nc --arg t "retro: issue #$ISSN" --arg b "$rbody" '{title:$t,body:$b}')" | jq -r '.number // empty') + if [ -n "$rnum" ]; then + echo "opened retro issue #$rnum" + post_to "$ISSN" "📝 Opened retro issue #$rnum." + trig "$rnum" "@senior please run this retrospective per the issue body." + else + echo "retro issue creation failed" + fi + exit 0 + fi + # --- @pm delegation: hand the build to a dev, or hand the finished PR to @qa for review --- # Only an explicit 'DELEGATE: @' line acts (never a prose mention). Fires via the PAT (TTOK) # so a new run starts; the built-in token cannot. Everything posts on the ISSUE — @pm never touches diff --git a/.gitea/workflows/scripts/run-agent.sh b/.gitea/workflows/scripts/run-agent.sh index 7f56b02..f0dba30 100755 --- a/.gitea/workflows/scripts/run-agent.sh +++ b/.gitea/workflows/scripts/run-agent.sh @@ -55,6 +55,10 @@ if [ "$MODE" = "comment" ]; then If anything is unclear or needs a decision at any phase, START your reply with '@ffaerber', ask specific questions, and do NOT emit a marker. Mentioning a teammate in prose does NOT act — only a DELEGATE line does. + RETRO — when the maintainer asks for a retrospective on this issue (e.g. 'run a retro', + '@pm retro'), briefly acknowledge and end your reply with EXACTLY one line: 'RETRO'. The + automation opens a retro issue (read this issue + its PR, distill learnings into LEARNINGS.md) + and assigns it. Emit RETRO only when explicitly asked. BREAKDOWN (a feature too big for one PR): in PHASE 1, propose a milestone name and the sub-task list, then ask '@ffaerber create these N sub-issues? reply yes.' ONLY after approval, end with: BEGIN_SUBTASKS @@ -102,10 +106,21 @@ else open pull requests yourself — that is automated for every branch you push. If the task is genuinely unclear, make NO changes and reply with specific questions instead." fi +# TEAM LEARNINGS — distilled from past retros (see the RETRO flow in publish.sh). Lives at the +# CALLER repo root as LEARNINGS.md, maintained by retro PRs. Injected into EVERY agent's prompt +# (capped) so past mistakes actually change future behavior — this is the feedback loop. +LEARN="" +[ -s LEARNINGS.md ] && LEARN=$(head -c 4000 LEARNINGS.md) +[ -n "$LEARN" ] && LEARN=" + TEAM LEARNINGS (distilled from past retros in this repo — APPLY them; they exist because a + previous task went wrong without them): + ${LEARN} +" PROMPT="You are @${NAME}, a member of an AI dev team working on this Gitea repository. YOUR ROLE: ${DESC} YOUR CAPABILITIES: model ${MODEL}. ${CAP} ${NOTE} +${LEARN} Your reply is posted as a comment already attributed to you (@${NAME}) — your name and avatar are shown by Gitea. Do NOT begin your reply with your own name, an '@${NAME}' header, or a '🤖/🔨 @you' diff --git a/README.md b/README.md index 84aaa7c..4af7805 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,15 @@ deploy-host SSH skill, live in the consuming repo under `.gitea/agent-skills/` `@pm` is the only agent that ever merges, and only under the `autopilot` label (its kill switch: remove the label mid-flight and the next step reverts to human control). +### Retros — the learning loop + +Ask `@pm` for a retrospective on any issue (e.g. **"@pm run a retro"**, typically when merging). The +automation opens a `retro: issue #N` issue and assigns `@senior`, who reads the full issue + PR +threads (via the `gitea-api` skill), distills what went wrong or slow, and appends one-line +`symptom → rule` bullets to **`LEARNINGS.md`** at the repo root — through the normal PR choreography, +so the retro itself gets reviewed. `LEARNINGS.md` is injected into **every agent's prompt** on every +run, so the lessons actually change future behavior (better delegation, fewer repeated misses). + ### Per-agent skill scoping Skills load **on-demand**: only a skill's one-line `description` ever appears in an agent's