Compare commits

...
5 Commits
Author SHA1 Message Date
ffaerber 8ae6abd9d0 Merge pull request 'chore: switch @lead/@ops from Anthropic to xAI grok-4.5' (#114) from chore/xai-grok-4.5 into main
ci / lint (push) Successful in 17s
2026-07-27 15:43:21 +02:00
Felix Faerber b30db8a4c9 chore: switch @lead/@ops from Anthropic to xAI grok-4.5
ci / lint (pull_request) Successful in 15s
Removes the last Anthropic-model usage from the agent registry.
XAI_API_KEY was already provisioned as an org secret; wire it into
the run-agent step and update every doc/comment that referenced
ANTHROPIC_API_KEY or claude-opus-4-8.
2026-07-27 16:43:05 +03:00
ffaerber 6955bc4f71 Merge pull request '@junior: publish.sh: PR-creation and push failures are silently swallowed' (#113) from ai/issue-112 into main
ci / lint (push) Successful in 32s
2026-07-27 15:33:43 +02:00
junior 0a06c813bc publish.sh: address review — fix PR-failure format, capture HTTP status, append activity to push failure 2026-07-27 13:32:47 +00:00
junior c4fb117b93 publish.sh: surface PR-open and git-push failures as issue comments
ci / lint (pull_request) Successful in 13s
2026-07-27 13:30:49 +00:00
8 changed files with 28 additions and 14 deletions
+1 -1
View File
@@ -211,7 +211,7 @@ jobs:
id: run id: run
env: env:
SCRIPTS: ${{ runner.temp }}/agents-scripts SCRIPTS: ${{ runner.temp }}/agents-scripts
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
# SELF_TOKEN = the RUNNING agent's OWN token (TOKEN_PM for @pm, TOKEN_OPS for @ops, …). # SELF_TOKEN = the RUNNING agent's OWN token (TOKEN_PM for @pm, TOKEN_OPS for @ops, …).
# Only this agent's token is placed in its process env, so no agent can act as another. # Only this agent's token is placed in its process env, so no agent can act as another.
# Powers the gitea-api / gitea-admin skills — each agent calls Gitea as itself. Every # Powers the gitea-api / gitea-admin skills — each agent calls Gitea as itself. Every
+2 -2
View File
@@ -25,7 +25,7 @@
"desc": "Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only)." "desc": "Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only)."
}, },
"lead": { "lead": {
"model": "anthropic/claude-opus-4-8", "model": "xai/grok-4.5",
"vision": true, "vision": true,
"mode": "pr", "mode": "pr",
"skills": [ "skills": [
@@ -43,7 +43,7 @@
"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." "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": { "ops": {
"model": "anthropic/claude-opus-4-8", "model": "xai/grok-4.5",
"vision": false, "vision": false,
"mode": "comment", "mode": "comment",
"skills": [ "skills": [
@@ -38,7 +38,7 @@ COST=${COST:-0}; INP=${INP:-0}; OUT=${OUT:-0}; CR=${CR:-0}; CW=${CW:-0}; RE=${RE
IN_TOTAL=$(( INP + CR + CW )) # total input context processed IN_TOTAL=$(( INP + CR + CW )) # total input context processed
# Cost label: ollama / ollama-cloud models are SUBSCRIPTION-billed (GPU-time against the plan, no # 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. # $/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. # Metered providers (xai/…) get the real dollar cost opencode computed.
case "${MODEL:-}" in case "${MODEL:-}" in
ollama*|*"/ollama"*) COSTF="subscription" ;; ollama*|*"/ollama"*) COSTF="subscription" ;;
*) COSTF=$(awk -v c="$COST" 'BEGIN{printf "$%.4f", c+0}') ;; *) COSTF=$(awk -v c="$COST" 'BEGIN{printf "$%.4f", c+0}') ;;
+1 -1
View File
@@ -42,7 +42,7 @@ PERM=$(jq -nc --argjson s "$SKILLS" '
# The provider `models:` maps are DERIVED from agents.json (the single source of truth, shared with # The provider `models:` maps are 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. # route.sh) so every model an agent is routed to is always declared in the provider config.
# `ollama-cloud/` prefix models go to the cloud provider; `ollama/` prefix models go to the local # `ollama-cloud/` prefix models go to the cloud provider; `ollama/` prefix models go to the local
# provider. Built-in providers (e.g. `anthropic/claude-opus-4-8` for @lead) are not derived here. # provider. Built-in providers (e.g. `xai/grok-4.5` for @lead/@ops) are not derived here.
# See issue #31. # See issue #31.
AGENTS_JSON="${SCRIPTS:-$(dirname -- "$0")}/agents.json" AGENTS_JSON="${SCRIPTS:-$(dirname -- "$0")}/agents.json"
CLOUD_MODELS=$(jq -r '[.[] | .model | select(startswith("ollama-cloud/")) | sub("^ollama-cloud/";"")] | map({(.):{}}) | add // {}' "$AGENTS_JSON") CLOUD_MODELS=$(jq -r '[.[] | .model | select(startswith("ollama-cloud/")) | sub("^ollama-cloud/";"")] | map({(.):{}}) | add // {}' "$AGENTS_JSON")
+18 -4
View File
@@ -334,7 +334,14 @@ if [ -n "$(git status --porcelain)" ]; then
git add -A git add -A
git commit -m "@$NAME: issue #$NUM" git commit -m "@$NAME: issue #$NUM"
fi fi
git push origin "HEAD:$BRANCH" || true if git push origin "HEAD:$BRANCH"; then
:
else
status=$?
echo "git push failed for $BRANCH (exit $status)"
post "$(printf '⚠️ Push to branch `%s` failed (git exit %s). The PR will not open until the push succeeds. Please check the Actions log.%s' "$BRANCH" "$status" "$activity")"
exit 0
fi
git fetch -q origin 2>/dev/null || true git fetch -q origin 2>/dev/null || true
prbody=$(printf '%s\n\n---\nResolves #%s' "$prdesc" "$NUM") prbody=$(printf '%s\n\n---\nResolves #%s' "$prdesc" "$NUM")
@@ -360,14 +367,21 @@ url=$(printf '%s' "$resp" | jq -r '.html_url // empty' 2>/dev/null)
prnum=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null) prnum=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null)
if [ -z "$url" ]; then if [ -z "$url" ]; then
title="@$NAME: $TITLE" title="@$NAME: $TITLE"
resp=$(curl -sS -X POST "${hdr[@]}" "$API/pulls" \ resp_body=/tmp/pr_create_resp.json
http_status=$(curl -sS -o "$resp_body" -w '%{http_code}' -X POST "${hdr[@]}" "$API/pulls" \
-d "$(jq -nc --arg t "$title" --arg h "$br" --arg b "$prbody" \ -d "$(jq -nc --arg t "$title" --arg h "$br" --arg b "$prbody" \
'{title:$t, head:$h, base:"main", body:$b}')") '{title:$t, head:$h, base:"main", body:$b}')")
echo "PR create ($br): $resp" resp=$(cat "$resp_body" 2>/dev/null || true)
echo "PR create ($br): HTTP $http_status$resp"
url=$(printf '%s' "$resp" | jq -r '.html_url // empty' 2>/dev/null) url=$(printf '%s' "$resp" | jq -r '.html_url // empty' 2>/dev/null)
prnum=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null) prnum=$(printf '%s' "$resp" | jq -r '.number // empty' 2>/dev/null)
fi fi
[ -z "$url" ] && { echo "PR open/lookup failed for $br — posting reply on issue instead"; post "$(printf '%s%s' "$reply" "$activity")"; exit 0; } if [ -z "$url" ]; then
err_msg=$(printf '%s' "$resp" | jq -r 'if type=="object" and .message then .message else "(no error message in response)" end' 2>/dev/null)
echo "PR open/lookup failed for $br — HTTP $http_status — response: $resp"
post "$(printf '⚠️ Failed to open PR for branch `%s`.\n\nHTTP status: %s\nGitea message: %s%s' "$br" "$http_status" "$err_msg" "$activity")"
exit 0
fi
# Posts to the PR thread when we have a PR number, else to the origin issue ($NUM). # Posts to the PR thread when we have a PR number, else to the origin issue ($NUM).
prpost() { prpost() {
+1 -1
View File
@@ -3,7 +3,7 @@
# plain-text reply (/tmp/agent_out.md) plus the raw event stream (/tmp/events.jsonl). # plain-text reply (/tmp/agent_out.md) plus the raw event stream (/tmp/events.jsonl).
# #
# Required env (provided by the workflow step): # Required env (provided by the workflow step):
# ANTHROPIC_API_KEY SELF_TOKEN NAME MODEL VISION MODE HAS_IMAGES BRANCH AUTOPILOT NUM TITLE # XAI_API_KEY SELF_TOKEN NAME MODEL VISION MODE HAS_IMAGES BRANCH AUTOPILOT NUM TITLE
# IBODY CMT # IBODY CMT
# FILES (the opencode -f image flags, from the imgs step output) # FILES (the opencode -f image flags, from the imgs step output)
# AUTOPILOT is 'true' when the issue carries the `autopilot` label (label-gated autopilot mode). # AUTOPILOT is 'true' when the issue carries the `autopilot` label (label-gated autopilot mode).
@@ -107,7 +107,7 @@ curl -sS -X POST -H "Authorization: token $SELF_TOKEN" "$API/orgs/{org}/labels"
3. Commit the standard caller so it gets the agents — `PUT /repos/{owner}/{repo}/contents/.gitea/workflows/ai-agent.yml` 3. Commit the standard caller so it gets the agents — `PUT /repos/{owner}/{repo}/contents/.gitea/workflows/ai-agent.yml`
with base64 `content`, `message`, `branch:"main"` (copy the exact caller from the `agents` repo README). with base64 `content`, `message`, `branch:"main"` (copy the exact caller from the `agents` repo README).
4. Add the agent bot users as collaborators: `PUT /repos/{owner}/{repo}/collaborators/{username}` (`{"permission":"write"}`). 4. Add the agent bot users as collaborators: `PUT /repos/{owner}/{repo}/collaborators/{username}` (`{"permission":"write"}`).
5. Ensure the repo can run agents — the org must hold the runtime secrets (ANTHROPIC_API_KEY, SELF_TOKEN, 5. Ensure the repo can run agents — the org must hold the runtime secrets (XAI_API_KEY, SELF_TOKEN,
TOKEN_* , OLLAMA_URL, OLLAMA_CLOUD_API_KEY); set any missing via the secrets calls above. TOKEN_* , OLLAMA_URL, OLLAMA_CLOUD_API_KEY); set any missing via the secrets calls above.
## Packages / container registry ## Packages / container registry
+3 -3
View File
@@ -10,9 +10,9 @@ Shared **AI dev-team** workflow for Gitea Actions, reusable across repos. It giv
| `@pm` | `ollama-cloud/minimax-m3:cloud` | yes | comment | `gitea-api` | Product manager & orchestrator — plans, picks the dev, hands finished PRs to `@qa`, reports back to the issue creator (autopilot: merges approved PRs itself). Issue thread only; never edits files, never reads the PR diff. | | `@pm` | `ollama-cloud/minimax-m3:cloud` | yes | comment | `gitea-api` | Product manager & orchestrator — plans, picks the dev, hands finished PRs to `@qa`, reports back to the issue creator (autopilot: merges approved PRs itself). Issue thread only; never edits files, never reads the PR diff. |
| `@junior` | `ollama-cloud/kimi-k2.7-code:cloud` | no | pr | — | Junior dev — small, low-risk changes (mostly YAML/compose/config). Text-only, cannot read images. Defers complex or image tasks to `@senior` or `@lead`. | | `@junior` | `ollama-cloud/kimi-k2.7-code:cloud` | no | pr | — | 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` | `ollama-cloud/glm-5.2:cloud` | no | pr | `gitea-api` | Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only). | | `@senior` | `ollama-cloud/glm-5.2:cloud` | no | pr | `gitea-api` | Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only). |
| `@lead` | `anthropic/claude-opus-4-8` | yes | pr | `gitea-api` | Tech lead — the hardest problems, architecture, and final calls. | | `@lead` | `xai/grok-4.5` | yes | pr | `gitea-api` | Tech lead — the hardest problems, architecture, and final calls. |
| `@qa` | `ollama-cloud/minimax-m3:cloud` | yes | comment | `gitea-api` | QA / reviewer — reads the PR diff, drives a headless browser (Playwright) to verify behavior; recommendations on the PR, pass/fail verdict on the issue. Never edits code, never merges. | | `@qa` | `ollama-cloud/minimax-m3:cloud` | yes | comment | `gitea-api` | QA / reviewer — reads the PR diff, drives a headless browser (Playwright) to verify behavior; recommendations on the PR, pass/fail verdict on the issue. Never edits code, never merges. |
| `@ops` | `anthropic/claude-opus-4-8` | no | comment | `gitea-admin` | Gitea operator — administers the instance itself (create orgs/users/repos, labels, secrets, scoped per-user tokens, bootstrap repos). Comments only; never edits code. Confirms before destructive actions. | | `@ops` | `xai/grok-4.5` | no | comment | `gitea-admin` | Gitea operator — administers the instance itself (create orgs/users/repos, labels, secrets, scoped per-user tokens, bootstrap repos). Comments only; never edits code. Confirms before destructive actions. |
| `@intern` | `ollama/ornith:35b` | no | pr | — | Intern — very basic tasks only, routed to the local Ollama model (`ornith:35b`). Text-only, cannot read images. Escalates anything non-trivial to `@junior`, `@senior` or `@lead`. | | `@intern` | `ollama/ornith:35b` | no | pr | — | Intern — very basic tasks only, routed to the local Ollama model (`ornith:35b`). Text-only, cannot read images. Escalates anything non-trivial to `@junior`, `@senior` or `@lead`. |
The registry `.gitea/workflows/scripts/agents.json` is the source of truth for this mapping — if you The registry `.gitea/workflows/scripts/agents.json` is the source of truth for this mapping — if you
@@ -111,7 +111,7 @@ points `$SCRIPTS` at it. Keep the workflow and its scripts moving together on `m
| Secret | For | | Secret | For |
|--------|-----| |--------|-----|
| `ANTHROPIC_API_KEY` | `@lead` (and `@pm`/`@senior`/`@qa` if on Claude) | | `XAI_API_KEY` | `@lead`, `@ops` (and any other agent switched to a `xai/…` model) |
| `OLLAMA_URL`, `OLLAMA_CLOUD_API_KEY` | local ornith / Ollama Cloud (gemma4, kimi-k2.7-code, glm-5.2, minimax-m3) | | `OLLAMA_URL`, `OLLAMA_CLOUD_API_KEY` | local ornith / Ollama Cloud (gemma4, kimi-k2.7-code, glm-5.2, minimax-m3) |
| `TOKEN_PM`,`TOKEN_SENIOR`,`TOKEN_JUNIOR`,`TOKEN_LEAD`,`TOKEN_QA` | **primary** — each agent's own Gitea-user PAT. The running agent gets *only its own* token (as `SELF_TOKEN`) so it posts, commits and comments as itself, and its `gitea-api` skill acts with its own scopes. Scopes: devs + `TOKEN_PM` carry `write:repository` (`@pm` is the only agent that merges, autopilot only); `TOKEN_QA` is `read:repository` + `write:issue` (reviews, never merges). | | `TOKEN_PM`,`TOKEN_SENIOR`,`TOKEN_JUNIOR`,`TOKEN_LEAD`,`TOKEN_QA` | **primary** — each agent's own Gitea-user PAT. The running agent gets *only its own* token (as `SELF_TOKEN`) so it posts, commits and comments as itself, and its `gitea-api` skill acts with its own scopes. Scopes: devs + `TOKEN_PM` carry `write:repository` (`@pm` is the only agent that merges, autopilot only); `TOKEN_QA` is `read:repository` + `write:issue` (reviews, never merges). |
| `TOKEN_OPS` | `@ops` only — the admin PAT behind the `gitea-admin` skill (create orgs/users/repos, manage labels & secrets, mint scoped tokens). Injected into the agent process only when the agent is `@ops`. | | `TOKEN_OPS` | `@ops` only — the admin PAT behind the `gitea-admin` skill (create orgs/users/repos, manage labels & secrets, mint scoped tokens). Injected into the agent process only when the agent is `@ops`. |