Compare commits

...
Author SHA1 Message Date
Felix FaerberandClaude Opus 4.8 509b10ac57 fix(routing): expose agent skills in the roster so pm routes by capability
The homelab health-check test mis-routed a node1 inspection to @junior (skills: []), which cannot
reach node1 — only @senior/@lead hold node1-ssh. Root cause: the roster shown to agents listed
desc + vision but not skills, so @pm had no way to route by capability (despite a comment claiming
it could). Add each teammate's skill names to the roster line. Names only; the scoped how-to detail
stays hidden via permission.skill.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 11:56:27 +03:00
ffaerber 9a3ca95f9e Merge pull request '@junior: Add runtime .agents-workflow/ checkout to .gitignore' (#36) from ai/issue-35 into main 2026-07-04 09:47:41 +02:00
junior f0f1152545 Add runtime .agents-workflow/ checkout to .gitignore 2026-07-04 07:45:27 +00:00
ffaerber 13148ea68c Merge pull request 'Single source of truth for agent model IDs (fixes #31)' (#32) from ai/issue-31 into main 2026-07-04 09:43:28 +02:00
ffaerber d0a4de9682 Merge pull request '@lead: Dev-agent runs can break later steps by touching the runtime .agents-workflow scripts checkout' (#34) from ai/issue-33 into main 2026-07-04 09:43:13 +02:00
lead 423a060816 fix(agent): run step scripts from outside the workspace so agents can't break the run
Stage the shared scripts into $RUNNER_TEMP and point $SCRIPTS there for every
step, so an agent that commits/deletes the in-tree .agents-workflow checkout no
longer destroys the scripts the post-agent steps run (issue #33). Scrub any
in-tree .agents-workflow artifact before publishing, and add a failure-safe
rescue step that opens a PR for pushed work when a run fails.
2026-07-04 07:34:53 +00:00
senior cafd36eae1 fix(agent): single source of truth for model IDs (route.sh + install-opencode.sh)
Extract the agent registry to a shared agents.json and have both
route.sh (agent → model) and install-opencode.sh (ollama-cloud provider
models map) derive from it, so the two lists can no longer drift.

route.sh now copies agents.json to /tmp/agents.json instead of an inline
heredoc. install-opencode.sh builds the ollama-cloud  map by
collecting every registry model with the  prefix and
stripping it — non-ollama-cloud models (e.g. @lead's anthropic/claude-opus,
the local ornith:35b) are excluded by construction.

Resolves #31.
2026-07-04 07:20:25 +00:00
8 changed files with 147 additions and 21 deletions
+53 -9
View File
@@ -42,6 +42,15 @@ jobs:
# .gitea/workflows/scripts/) are NOT on disk yet. Check this repo out into a separate subdir # .gitea/workflows/scripts/) are NOT on disk yet. Check this repo out into a separate subdir
# and run the scripts from $SCRIPTS. Pinned to @main to match the caller's # and run the scripts from $SCRIPTS. Pinned to @main to match the caller's
# `uses: …/agent.yml@main`, so the scripts and the workflow always move together. # `uses: …/agent.yml@main`, so the scripts and the workflow always move together.
#
# actions/checkout requires `path` to be inside the workspace, so this necessarily lands the
# clone at `.agents-workflow/` INSIDE the tree the dev agent later edits. That checkout dir is
# untracked and looks like a stray artifact: an agent that commits it as a gitlink or
# `rm -rf`s it as "leftover" would destroy the very scripts the post-agent steps run, breaking
# the run with exit 127 and stranding pushed work with no PR (issue #33). To make the run
# immune, the next step copies the scripts to a stable location OUTSIDE the workspace
# (${{ runner.temp }}) and every later step runs from $SCRIPTS there — so nothing the agent
# does to the working tree can break the run's own execution environment.
- name: Fetch shared agent scripts (this repo) - name: Fetch shared agent scripts (this repo)
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
@@ -50,10 +59,24 @@ jobs:
path: .agents-workflow path: .agents-workflow
token: ${{ secrets.GITEA_TOKEN }} token: ${{ secrets.GITEA_TOKEN }}
# Copy the step scripts out of the workspace so the agent cannot break them (issue #33).
# $SCRIPTS points here for every subsequent step, NOT into the in-tree .agents-workflow/.
- name: Stage shared scripts outside the workspace
env:
SRC: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts
DST: ${{ runner.temp }}/agents-scripts
run: |
set -eu
rm -rf "$DST"
mkdir -p "$DST"
cp -a "$SRC"/. "$DST"/
chmod -R a+rx "$DST" || true
echo "staged $(ls -1 "$DST" | wc -l) scripts at $DST"
- name: Route agent + prepare branch - name: Route agent + prepare branch
id: prep id: prep
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
BODY: ${{ github.event.comment.body }} # event text via env, never inline in shell BODY: ${{ github.event.comment.body }} # event text via env, never inline in shell
IBODY: ${{ github.event.issue.body }} IBODY: ${{ github.event.issue.body }}
# Comment-vs-issue discriminator. Do NOT use github.event_name here: this is a REUSABLE # Comment-vs-issue discriminator. Do NOT use github.event_name here: this is a REUSABLE
@@ -73,7 +96,7 @@ jobs:
- name: Install opencode + provider config (+ Playwright MCP for browser agents) - name: Install opencode + provider config (+ Playwright MCP for browser agents)
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
OLLAMA_URL: ${{ secrets.OLLAMA_URL }} OLLAMA_URL: ${{ secrets.OLLAMA_URL }}
OLLAMA_CLOUD_API_KEY: ${{ secrets.OLLAMA_CLOUD_API_KEY }} OLLAMA_CLOUD_API_KEY: ${{ secrets.OLLAMA_CLOUD_API_KEY }}
NAME: ${{ steps.prep.outputs.name }} NAME: ${{ steps.prep.outputs.name }}
@@ -90,7 +113,7 @@ jobs:
# All three secrets are passed via env and never inlined into shell — this shared workflow # All three secrets are passed via env and never inlined into shell — this shared workflow
# runs in repos that don't have them and must not fail there. # runs in repos that don't have them and must not fail there.
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
SWARM_HOST: ${{ secrets.SWARM_HOST }} SWARM_HOST: ${{ secrets.SWARM_HOST }}
SWARM_USER: ${{ secrets.SWARM_USER }} SWARM_USER: ${{ secrets.SWARM_USER }}
SSH_PRIV_KEY: ${{ secrets.SSH_PRIV_KEY }} SSH_PRIV_KEY: ${{ secrets.SSH_PRIV_KEY }}
@@ -104,14 +127,14 @@ jobs:
# Only emitted when AGENT_TOKEN is actually present, so repos without it don't get a # Only emitted when AGENT_TOKEN is actually present, so repos without it don't get a
# broken skill. The token is passed via env and never inlined into shell. # broken skill. The token is passed via env and never inlined into shell.
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }} AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }}
run: bash "$SCRIPTS/skill-gitea-api.sh" run: bash "$SCRIPTS/skill-gitea-api.sh"
- name: Inspect / fetch image attachments (download only for vision agents) - name: Inspect / fetch image attachments (download only for vision agents)
id: imgs id: imgs
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
GT: ${{ secrets.GITEA_TOKEN }} GT: ${{ secrets.GITEA_TOKEN }}
NUM: ${{ github.event.issue.number }} NUM: ${{ github.event.issue.number }}
VISION: ${{ steps.prep.outputs.vision }} VISION: ${{ steps.prep.outputs.vision }}
@@ -119,7 +142,7 @@ jobs:
- name: Fetch the full issue thread (shared memory) - name: Fetch the full issue thread (shared memory)
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
GT: ${{ secrets.GITEA_TOKEN }} GT: ${{ secrets.GITEA_TOKEN }}
NUM: ${{ github.event.issue.number }} NUM: ${{ github.event.issue.number }}
run: bash "$SCRIPTS/fetch-thread.sh" run: bash "$SCRIPTS/fetch-thread.sh"
@@ -127,7 +150,7 @@ jobs:
- name: Run agent - name: Run agent
id: run id: run
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# AGENT_TOKEN powers the `gitea-api` skill (cross-repo issue/PR/Actions read+write). # AGENT_TOKEN powers the `gitea-api` skill (cross-repo issue/PR/Actions read+write).
# It is already a required secret for the delegation step below; exposing it here too # It is already a required secret for the delegation step below; exposing it here too
@@ -149,13 +172,13 @@ jobs:
- name: Build activity log (tool calls + reasoning) from the event stream - name: Build activity log (tool calls + reasoning) from the event stream
id: log id: log
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
MODE: ${{ steps.prep.outputs.mode }} MODE: ${{ steps.prep.outputs.mode }}
run: bash "$SCRIPTS/build-activity-log.sh" run: bash "$SCRIPTS/build-activity-log.sh"
- name: Publish — PR (dev agents) or comment (pm), always reply in the issue - name: Publish — PR (dev agents) or comment (pm), always reply in the issue
env: env:
SCRIPTS: ${{ github.workspace }}/.agents-workflow/.gitea/workflows/scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
GT: ${{ secrets.GITEA_TOKEN }} GT: ${{ secrets.GITEA_TOKEN }}
AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }} AGENT_TOKEN: ${{ secrets.AGENT_TOKEN }}
TOKEN_PM: ${{ secrets.TOKEN_PM }} TOKEN_PM: ${{ secrets.TOKEN_PM }}
@@ -171,6 +194,27 @@ jobs:
NEW: ${{ steps.prep.outputs.new }} NEW: ${{ steps.prep.outputs.new }}
run: bash "$SCRIPTS/publish.sh" run: bash "$SCRIPTS/publish.sh"
# Failure-safe: if any step above failed AFTER a dev agent already pushed commits, the normal
# Publish step never ran, so the work would be stranded on the branch with no PR (issue #33).
# This best-effort step opens a PR for the pushed branch so nothing is silently lost. Runs from
# $SCRIPTS (outside the workspace) so it works even if the tree was mangled by the agent.
- name: Rescue — open a PR for pushed work if the run failed
if: failure()
env:
SCRIPTS: ${{ runner.temp }}/agents-scripts
GT: ${{ secrets.GITEA_TOKEN }}
TOKEN_PM: ${{ secrets.TOKEN_PM }}
TOKEN_SENIOR: ${{ secrets.TOKEN_SENIOR }}
TOKEN_JUNIOR: ${{ secrets.TOKEN_JUNIOR }}
TOKEN_LEAD: ${{ secrets.TOKEN_LEAD }}
TOKEN_QA: ${{ secrets.TOKEN_QA }}
NAME: ${{ steps.prep.outputs.name }}
MODE: ${{ steps.prep.outputs.mode }}
NUM: ${{ github.event.issue.number }}
TITLE: ${{ github.event.issue.title }}
BRANCH: ${{ steps.prep.outputs.branch }}
run: bash "$SCRIPTS/rescue-pr.sh" || true
- name: Mark done with 🚀 (remove 👀) - name: Mark done with 🚀 (remove 👀)
env: env:
GT: ${{ secrets.GITEA_TOKEN }} GT: ${{ secrets.GITEA_TOKEN }}
+7
View File
@@ -0,0 +1,7 @@
{
"pm": {"model":"ollama-cloud/gemma4:cloud","vision":true, "mode":"comment","skills":["gitea-api"],"desc":"Product manager — research, plan, ask clarifying questions, and decide which dev should do the work. Comments only; never edits files."},
"junior": {"model":"ollama-cloud/kimi-k2.7-code:cloud","vision":false,"mode":"pr", "skills":[],"desc":"Junior dev — small, low-risk changes (mostly YAML/compose/config). Text-only, cannot read images. Defers complex or image tasks to @senior or @lead."},
"senior": {"model":"ollama-cloud/glm-5.2:cloud","vision":false,"mode":"pr", "skills":["gitea-api","node1-ssh"],"desc":"Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only)."},
"lead": {"model":"anthropic/claude-opus-4-8","vision":true, "mode":"pr", "skills":["gitea-api","node1-ssh"],"desc":"Tech lead — the hardest problems, architecture, and final calls."},
"qa": {"model":"ollama-cloud/minimax-m3:cloud","vision":true, "mode":"comment","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."}
}
+8 -2
View File
@@ -28,10 +28,16 @@ SKILLS="${SKILLS:-[]}"
PERM=$(jq -nc --argjson s "$SKILLS" ' PERM=$(jq -nc --argjson s "$SKILLS" '
{skill: ( {"*":"deny"} + (reduce $s[] as $k ({}; . + {($k):"allow"})) )}') {skill: ( {"*":"deny"} + (reduce $s[] as $k ({}; . + {($k):"allow"})) )}')
# Two ollama providers: local self-hosted (ornith) + Ollama Cloud (gemma4/kimi-k2.7-code/glm-5.2/minimax-m3). # Two ollama providers: local self-hosted (ornith) + Ollama Cloud (gemma4/kimi-k2.7-code/glm-5.2/minimax-m3).
jq -n --argjson mcp "$MCP" --argjson perm "$PERM" --arg url "$OLLAMA_URL" --arg ckey "$OLLAMA_CLOUD_API_KEY" '{ # The ollama-cloud `models:` map is DERIVED from agents.json (the single source of truth, shared with
# route.sh) so every model an agent is routed to is always declared in the provider config. Only the
# `ollama-cloud/` provider prefix models participate — e.g. `anthropic/claude-opus-4-8` (@lead) is a
# built-in provider and `ornith:35b` is local-only, neither belongs here. See issue #31.
AGENTS_JSON="${SCRIPTS:-$(dirname -- "$0")}/agents.json"
CLOUD_MODELS=$(jq -r '[.[] | .model | select(startswith("ollama-cloud/")) | sub("^ollama-cloud/";"")] | map({(.):{}}) | add // {}' "$AGENTS_JSON")
jq -n --argjson mcp "$MCP" --argjson perm "$PERM" --argjson cloud "$CLOUD_MODELS" --arg url "$OLLAMA_URL" --arg ckey "$OLLAMA_CLOUD_API_KEY" '{
provider: { provider: {
ollama: {npm:"@ai-sdk/openai-compatible", options:{baseURL:($url+"/v1")}, models:{"ornith:35b":{}}}, ollama: {npm:"@ai-sdk/openai-compatible", options:{baseURL:($url+"/v1")}, models:{"ornith:35b":{}}},
"ollama-cloud": {npm:"@ai-sdk/openai-compatible", options:{baseURL:"https://ollama.com/v1", apiKey:$ckey}, models:{"glm-5.2:cloud":{},"gemma4:cloud":{},"kimi-k2.7-code:cloud":{},"minimax-m3:cloud":{}}} "ollama-cloud": {npm:"@ai-sdk/openai-compatible", options:{baseURL:"https://ollama.com/v1", apiKey:$ckey}, models:$cloud}
}, },
permission: $perm, permission: $perm,
mcp: $mcp mcp: $mcp
+12
View File
@@ -98,6 +98,18 @@ if [ "$MODE" != "pr" ]; then
exit 0 exit 0
fi fi
# Scrub the runtime scripts checkout (.agents-workflow) from the tree so it never lands in a
# commit/PR and never confuses the git ops below (issue #33). The scripts we run live outside the
# workspace ($SCRIPTS -> runner.temp), so removing this in-tree copy is always safe. Handle every
# way an agent might have left it: untracked dir, tracked files, or a committed gitlink/submodule.
if git ls-files --error-unmatch .agents-workflow >/dev/null 2>&1 || \
[ -n "$(git ls-files .agents-workflow 2>/dev/null)" ]; then
git rm -r --cached --quiet --ignore-unmatch .agents-workflow 2>/dev/null || true
fi
git config -f .gitmodules --remove-section submodule..agents-workflow 2>/dev/null || true
[ -s .gitmodules ] || rm -f .gitmodules 2>/dev/null || true
rm -rf .agents-workflow 2>/dev/null || true
# The agent may have committed on the starting branch AND/OR created extra # The agent may have committed on the starting branch AND/OR created extra
# ai/issue-N-<slug> branches. Commit any leftover on the current branch, push it, then # ai/issue-N-<slug> branches. Commit any leftover on the current branch, push it, then
# open a PR for EVERY ai/issue-N* branch that has commits beyond main. # open a PR for EVERY ai/issue-N* branch that has commits beyond main.
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# Failure-safe rescue: when a run FAILED after a dev agent already pushed commits, the normal
# Publish step never ran and the work would be stranded on the branch with no PR (issue #33).
# This opens a PR for the pushed branch so nothing is silently lost. It is strictly best-effort:
# every failure here is swallowed (the caller also appends `|| true`) so it can never itself break
# 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
# NAME MODE NUM TITLE BRANCH GITHUB_SERVER_URL GITHUB_REPOSITORY
set +e
# Only dev agents (mode=pr) ever push a branch to rescue.
[ "${MODE:-}" = "pr" ] || { echo "rescue: comment-mode agent, nothing to rescue"; exit 0; }
[ -n "${BRANCH:-}" ] || { echo "rescue: no branch known, skipping"; exit 0; }
# 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="";;
esac
[ -z "$TOK" ] && TOK="$GT"
API="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}"
hdr=(-H "Authorization: token $TOK" -H "Content-Type: application/json")
git fetch -q origin 2>/dev/null || true
# Nothing to rescue unless the branch exists on the remote with commits beyond main.
ahead=$(git rev-list --count "origin/main..origin/$BRANCH" 2>/dev/null || echo 0)
if [ "${ahead:-0}" -eq 0 ]; then
echo "rescue: no pushed commits on origin/$BRANCH beyond main — nothing to rescue"
exit 0
fi
echo "rescue: origin/$BRANCH is $ahead commit(s) ahead of main — ensuring a PR exists"
# Idempotent: Gitea ignores ?head=, so match the head branch client-side.
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")
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)
fi
if [ -n "$url" ]; then
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}')" \
-w '\nrescue comment -> HTTP %{http_code}\n' || true
else
echo "rescue: could not open/find a PR for $BRANCH"
fi
exit 0
+6 -9
View File
@@ -10,21 +10,18 @@
set -eu set -eu
# --- agent registry: model + capabilities + mode + role + skills --- # --- agent registry: model + capabilities + mode + role + skills ---
# The registry is the SINGLE SOURCE OF TRUTH, kept in agents.json next to this
# script. install-opencode.sh derives its ollama-cloud provider `models:` map
# from the same file, so an agent's model can never be missing from the provider
# config — drift is impossible by construction. See issue #31.
# `skills` is the allow-list of opencode Skills each agent may load. It scopes the # `skills` is the allow-list of opencode Skills each agent may load. It scopes the
# `permission.skill` block written into opencode.json (see install-opencode.sh) so an agent only # `permission.skill` block written into opencode.json (see install-opencode.sh) so an agent only
# ever sees (and can load) the skills relevant to its role. Skills NOT listed here are hidden from # ever sees (and can load) the skills relevant to its role. Skills NOT listed here are hidden from
# that agent entirely — not even the one-line summary appears in its <available_skills>, so the # that agent entirely — not even the one-line summary appears in its <available_skills>, so the
# full API/how-to detail never reaches an agent that shouldn't act on it. A teammate can still learn # full API/how-to detail never reaches an agent that shouldn't act on it. A teammate can still learn
# *that* another agent has a capability from the roster and ask them to use it. # *that* another agent has a capability from the roster and ask them to use it.
cat > /tmp/agents.json <<'JSON' AGENTS_JSON="${SCRIPTS:-$(dirname -- "$0")}/agents.json"
{ cp "$AGENTS_JSON" /tmp/agents.json
"pm": {"model":"ollama-cloud/gemma4:cloud","vision":true, "mode":"comment","skills":["gitea-api"],"desc":"Product manager — research, plan, ask clarifying questions, and decide which dev should do the work. Comments only; never edits files."},
"junior": {"model":"ollama-cloud/kimi-k2.7-code:cloud","vision":false,"mode":"pr", "skills":[],"desc":"Junior dev — small, low-risk changes (mostly YAML/compose/config). Text-only, cannot read images. Defers complex or image tasks to @senior or @lead."},
"senior": {"model":"ollama-cloud/glm-5.2:cloud","vision":false,"mode":"pr", "skills":["gitea-api","node1-ssh"],"desc":"Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only)."},
"lead": {"model":"anthropic/claude-opus-4-8","vision":true, "mode":"pr", "skills":["gitea-api","node1-ssh"],"desc":"Tech lead — the hardest problems, architecture, and final calls."},
"qa": {"model":"ollama-cloud/minimax-m3:cloud","vision":true, "mode":"comment","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."}
}
JSON
# On a new issue, @pm auto-assesses. On a comment, route by the @mention. # On a new issue, @pm auto-assesses. On a comment, route by the @mention.
# A comment event has a comment id (CID); an issue-opened event does not. (event_name is unreliable # A comment event has a comment id (CID); an issue-opened event does not. (event_name is unreliable
# here — see agent.yml: this reusable workflow sees it as 'workflow_call'.) # here — see agent.yml: this reusable workflow sees it as 'workflow_call'.)
+4 -1
View File
@@ -10,7 +10,10 @@ set -u
[ -z "$CMT" ] && CMT="(a new issue was just opened — assess it)" [ -z "$CMT" ] && CMT="(a new issue was just opened — assess it)"
THREAD=$(cat /tmp/thread.md 2>/dev/null); [ -z "$THREAD" ] && THREAD="(no prior comments)" THREAD=$(cat /tmp/thread.md 2>/dev/null); [ -z "$THREAD" ] && THREAD="(no prior comments)"
DESC=$(jq -r --arg a "$NAME" '.[$a].desc' /tmp/agents.json) DESC=$(jq -r --arg a "$NAME" '.[$a].desc' /tmp/agents.json)
ROSTER=$(jq -r 'to_entries | map("- @\(.key): \(.value.desc) (vision: \(.value.vision))") | join("\n")' /tmp/agents.json) # Include each teammate's skills so an agent (esp. @pm) can route by capability — e.g. only
# @senior/@lead hold node1-ssh, so a node1 task must not go to @junior. Skill *names* only; the
# scoped how-to detail stays hidden per the permission.skill allow-list.
ROSTER=$(jq -r 'to_entries | map("- @\(.key): \(.value.desc) (vision: \(.value.vision); skills: \(.value.skills | if length>0 then join(", ") else "none" end))") | join("\n")' /tmp/agents.json)
if [ "$VISION" = "true" ]; then CAP="You CAN read images attached to the issue."; else CAP="You CANNOT read images — you are a text-only model."; fi if [ "$VISION" = "true" ]; then CAP="You CAN read images attached to the issue."; else CAP="You CANNOT read images — you are a text-only model."; fi
NOTE="" NOTE=""
if [ "$VISION" != "true" ] && [ "${HAS_IMAGES:-0}" -gt 0 ]; then if [ "$VISION" != "true" ] && [ "${HAS_IMAGES:-0}" -gt 0 ]; then
+1
View File
@@ -1 +1,2 @@
.env .env
.agents-workflow/