agents: fix review findings — thread attribution, races, rescue stall, docs, CI
ci / lint (push) Skipped
ci / lint (pull_request) Successful in 11s
ci / lint (push) Skipped
ci / lint (pull_request) Successful in 11s
Fixes from a full repo review: - fetch-thread.sh: attribute every comment to its REAL author (@pm/@qa/… — agents post as their own users now); the old "🤖 @name line at the top" hint pointed at headers we removed, leaving every teammate comment anonymous. Also strip the hidden loop-prevention marker from bodies (prompt noise). - agent.yml: per-issue concurrency group (queued, no cancel) — two quick comments on one issue no longer race the same ai/issue-N branch. - rescue-pr.sh: after opening a rescue PR, hand it back into the flow with an unmarked @pm trigger (the pm→qa choreography previously stalled silently on any rescued run); drop the old "🤖 **@name**" header style; add ops to the token case; mark the status note with the hidden marker. - README: token table said "@qa merges / TOKEN_QA needs write:repository" — inverted since the PM-orchestrated flow (@pm merges, autopilot only; @qa is read-only). Updated the agent table (descs, node1-ssh moved to homelab) and added a "How a task flows" section. - agents.json: pm/qa descs now describe the orchestrator/reviewer roles (these feed the roster prompt agents route by). - NEW ci.yml: bash -n + shellcheck(-S error) on every script, YAML-parse on every workflow, agents.json schema check — the ${x:-{}} brace bug would have been caught here before it shipped. - install-opencode.sh: pin opencode (default 1.17.13, override via OPENCODE_VERSION) — a breaking release no longer takes down every agent. - build-activity-log.sh: ollama/ollama-cloud models are subscription-billed (no $/token exists) — label the footer "subscription" instead of a misleading $0.0000; metered (anthropic) models keep the real dollar cost. - route.sh: document that mention-priority is list-order and load-bearing for the flow's trigger comments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
88237d3f4d
commit
9b9e1e945d
@@ -6,7 +6,7 @@
|
||||
"skills": [
|
||||
"gitea-api"
|
||||
],
|
||||
"desc": "Product manager — research, plan, ask clarifying questions, and decide which dev should do the work. Comments only; never edits files."
|
||||
"desc": "Product manager & orchestrator — plans and picks the dev, hands each finished PR to @qa for review, and reports back to the issue creator (in autopilot it merges approved PRs itself). Works from the issue thread only — comments only, never edits files, never reads the PR diff."
|
||||
},
|
||||
"junior": {
|
||||
"model": "ollama-cloud/kimi-k2.7-code:cloud",
|
||||
@@ -40,7 +40,7 @@
|
||||
"skills": [
|
||||
"gitea-api"
|
||||
],
|
||||
"desc": "QA — verifies things work. Drives a headless browser (Playwright) to open a URL/web app, click through it, screenshot, and report bugs or confirm behavior. Comments findings; opens no PRs."
|
||||
"desc": "QA / reviewer — reviews PRs: reads the diff, drives a headless browser (Playwright) to verify behavior, posts specific recommendations on the PR and the pass/fail verdict on the issue. Never edits code, never merges."
|
||||
},
|
||||
"ops": {
|
||||
"model": "anthropic/claude-opus-4-8",
|
||||
|
||||
@@ -36,14 +36,20 @@ read -r COST INP OUT CR CW RE < <(jq -rs '
|
||||
| @tsv' "$E" 2>/dev/null)
|
||||
COST=${COST:-0}; INP=${INP:-0}; OUT=${OUT:-0}; CR=${CR:-0}; CW=${CW:-0}; RE=${RE:-0}
|
||||
IN_TOTAL=$(( INP + CR + CW )) # total input context processed
|
||||
COSTF=$(awk -v c="$COST" 'BEGIN{printf "$%.4f", c+0}')
|
||||
# Cost label: ollama / ollama-cloud models are SUBSCRIPTION-billed (GPU-time against the plan, no
|
||||
# $/token price exists), so a "$0.0000" there would be misleading — label it a subscription instead.
|
||||
# Metered providers (anthropic/…) get the real dollar cost opencode computed.
|
||||
case "${MODEL:-}" in
|
||||
ollama*|*"/ollama"*) COSTF="subscription" ;;
|
||||
*) COSTF=$(awk -v c="$COST" 'BEGIN{printf "$%.4f", c+0}') ;;
|
||||
esac
|
||||
echo "usage: in=$IN_TOTAL out=$OUT cost=$COSTF (fresh=$INP cache_r=$CR cache_w=$CW reasoning=$RE); tools=$n"
|
||||
|
||||
{
|
||||
if [ "$n" -gt 0 ]; then
|
||||
printf '\n\n<details>\n<summary>🔧 %s tool calls · in %s · out %s · %s</summary>\n\n' "$n" "$IN_TOTAL" "$OUT" "$COSTF"
|
||||
cat /tmp/tools.md
|
||||
printf '\n\n<sub>tokens — input %s (fresh %s · cache %sw / %sr) · output %s · reasoning %s · **cost %s**</sub>\n</details>' \
|
||||
printf '\n\n<sub>tokens — input %s (fresh %s · cache %sw / %sr) · output %s · reasoning %s · **%s**</sub>\n</details>' \
|
||||
"$IN_TOTAL" "$INP" "$CW" "$CR" "$OUT" "$RE" "$COSTF"
|
||||
else
|
||||
printf '\n\n<sub>💰 **%s** · in %s · out %s tokens (cache %sw / %sr)</sub>' "$COSTF" "$IN_TOTAL" "$OUT" "$CW" "$CR"
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fetch the full issue thread (shared memory) into /tmp/thread.md.
|
||||
# Agents post as their OWN Gitea users, so .user.login IS the agent name — attribute each comment
|
||||
# to its real author (@pm/@qa/@junior/…). Strip the hidden `<!-- 🤖 … -->` loop-prevention marker
|
||||
# from bodies — it's plumbing, not conversation, and would just waste prompt tokens.
|
||||
#
|
||||
# Required env (provided by the workflow step): GT NUM GITHUB_SERVER_URL GITHUB_REPOSITORY
|
||||
set -eu
|
||||
@@ -7,9 +10,8 @@ set -eu
|
||||
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||
curl -sS -H "Authorization: token $GT" "$API/issues/$NUM/comments?limit=100" 2>/dev/null \
|
||||
| jq -r '.[] |
|
||||
( if (.body | test("delegated by")) then "an automated delegation"
|
||||
elif (.user.login == "ffaerber") then "ffaerber (the maintainer / you)"
|
||||
else "an AI teammate — the specific one is named in the 🤖 @name line at the top of the comment"
|
||||
end ) as $who |
|
||||
"### comment by \($who):\n\(.body)\n"' > /tmp/thread.md 2>/dev/null || true
|
||||
( if (.user.login == "ffaerber") then "@ffaerber (the maintainer)"
|
||||
else "@" + .user.login end ) as $who |
|
||||
"### comment by \($who):\n\(.body | gsub("\\s*<!-- 🤖 agent reply — do not trigger -->"; ""))\n"' \
|
||||
> /tmp/thread.md 2>/dev/null || true
|
||||
echo "thread comments fetched: $(grep -c '^### comment by ' /tmp/thread.md 2>/dev/null || echo 0)"
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
# GITHUB_PATH HOME
|
||||
set -eu
|
||||
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
# PIN the opencode version: an unpinned `latest` means a breaking release (CLI flags, or the
|
||||
# --format json event schema that build-activity-log.sh parses) breaks every agent in every repo
|
||||
# at once. Bump deliberately by changing this default (or set OPENCODE_VERSION in the step env).
|
||||
OPENCODE_VERSION="${OPENCODE_VERSION:-1.17.13}"
|
||||
curl -fsSL https://opencode.ai/install | bash -s -- --version "$OPENCODE_VERSION"
|
||||
echo "$HOME/.opencode/bin" >> "$GITHUB_PATH"
|
||||
mkdir -p ~/.config/opencode
|
||||
# Playwright browser MCP only for agents that need to drive a web app
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# the run. Comment-only roles (pm/qa) push nothing, so they are skipped.
|
||||
#
|
||||
# Required env (provided by the workflow step):
|
||||
# GT TOKEN_PM TOKEN_SENIOR TOKEN_JUNIOR TOKEN_LEAD TOKEN_QA
|
||||
# GT TOKEN_PM TOKEN_SENIOR TOKEN_JUNIOR TOKEN_LEAD TOKEN_QA TOKEN_OPS
|
||||
# NAME MODE NUM TITLE BRANCH GITHUB_SERVER_URL GITHUB_REPOSITORY
|
||||
set +e
|
||||
|
||||
@@ -17,9 +17,11 @@ set +e
|
||||
# Post/PR as the agent's OWN Gitea user when its token is configured; else the built-in bot.
|
||||
case "$NAME" in
|
||||
pm) TOK="$TOKEN_PM";; senior) TOK="$TOKEN_SENIOR";; junior) TOK="$TOKEN_JUNIOR";;
|
||||
lead) TOK="$TOKEN_LEAD";; qa) TOK="$TOKEN_QA";; *) TOK="";;
|
||||
lead) TOK="$TOKEN_LEAD";; qa) TOK="$TOKEN_QA";; ops) TOK="$TOKEN_OPS";; *) TOK="";;
|
||||
esac
|
||||
[ -z "$TOK" ] && TOK="$GT"
|
||||
# Trigger token: the @pm hand-back below must FIRE a new run, which the built-in token cannot.
|
||||
TTOK="$TOK"; [ "$TTOK" = "$GT" ] && TTOK=""
|
||||
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
|
||||
hdr=(-H "Authorization: token $TOK" -H "Content-Type: application/json")
|
||||
|
||||
@@ -38,18 +40,30 @@ resp=$(curl -sS "${hdr[@]}" "$API/pulls?state=open&limit=50" \
|
||||
| jq -r --arg br "$BRANCH" 'if type=="array" then (map(select(.head.ref==$br)) | .[0] // empty) else empty end' 2>/dev/null)
|
||||
url=$(printf '%s' "$resp" | jq -r '.html_url // empty' 2>/dev/null)
|
||||
if [ -z "$url" ]; then
|
||||
body=$(printf 'The run failed before it could publish, but pushed work exists on this branch — opening a PR so it is not lost.\n\n---\nResolves #%s · 🤖 @%s (auto-rescued after a failed run)' "$NUM" "$NAME")
|
||||
body=$(printf 'The run failed before it could publish, but pushed work exists on this branch — opening a PR so it is not lost.\n\n---\nResolves #%s (auto-rescued after a failed run)' "$NUM")
|
||||
resp=$(curl -sS -X POST "${hdr[@]}" "$API/pulls" \
|
||||
-d "$(jq -nc --arg t "@$NAME: $TITLE" --arg h "$BRANCH" --arg b "$body" \
|
||||
'{title:$t, head:$h, base:"main", body:$b}')")
|
||||
echo "rescue PR create ($BRANCH): $resp"
|
||||
url=$(printf '%s' "$resp" | jq -r '.html_url // empty' 2>/dev/null)
|
||||
prnum=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null)
|
||||
else
|
||||
prnum=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null)
|
||||
fi
|
||||
|
||||
if [ -n "$url" ]; then
|
||||
# Status note on the issue (marked — must not trigger)…
|
||||
curl -sS -X POST "${hdr[@]}" "$API/issues/$NUM/comments" \
|
||||
-d "$(jq -nc --arg b "$(printf '🤖 **@%s** — ⚠️ the run failed, but your pushed work was not lost — a PR was opened for branch \`%s\`:\n- %s' "$NAME" "$BRANCH" "$url")" '{body:$b}')" \
|
||||
-d "$(jq -nc --arg b "$(printf '⚠️ The run failed, but the pushed work was not lost — a PR was opened for branch \`%s\`:\n- %s\n\n<!-- 🤖 agent reply — do not trigger -->' "$BRANCH" "$url")" '{body:$b}')" \
|
||||
-w '\nrescue comment -> HTTP %{http_code}\n' || true
|
||||
# …then hand the rescued PR back into the flow: without this, the pm→qa choreography would stall
|
||||
# here (the normal "PR ready" trigger never fired). Unmarked + PAT so it starts @pm's run.
|
||||
if [ -n "$TTOK" ]; then
|
||||
curl -sS -X POST -H "Authorization: token $TTOK" -H "Content-Type: application/json" \
|
||||
"$API/issues/$NUM/comments" \
|
||||
-d "$(jq -nc --arg b "@pm — PR #${prnum:-?} was auto-rescued after a failed run (issue #$NUM). Please route it for review." '{body:$b}')" \
|
||||
-w '\nrescue trigger @pm -> HTTP %{http_code}\n' || true
|
||||
fi
|
||||
else
|
||||
echo "rescue: could not open/find a PR for $BRANCH"
|
||||
fi
|
||||
|
||||
@@ -27,6 +27,10 @@ cp "$AGENTS_JSON" /tmp/agents.json
|
||||
# here — see agent.yml: this reusable workflow sees it as 'workflow_call'.)
|
||||
if [ -n "$CID" ]; then scan="$BODY"; else scan="$IBODY"; fi
|
||||
name=""
|
||||
# FIRST MATCH IN THIS LIST ORDER WINS when a comment mentions several agents. The order is
|
||||
# load-bearing for the flow's trigger comments: "@pm — @qa approved …" must route to @pm (pm is
|
||||
# checked first), while "@junior please address @qa's review …" must route to the dev (devs are
|
||||
# checked before qa). If you add an agent or reword a trigger in publish.sh, re-check this order.
|
||||
for a in pm junior senior lead qa ops; do
|
||||
case "$scan" in *"@$a"*) name=$a; break;; esac
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user