ci / lint (push) Skipped
ci / lint (pull_request) Successful in 11s
New learning step: ask @pm for a retrospective on any issue and the system turns what happened into prompt-visible rules for future runs. - run-agent.sh: @pm gains a RETRO marker (emit only when the maintainer asks); LEARNINGS.md (caller repo root, capped at 4KB) is injected into EVERY agent's prompt as "TEAM LEARNINGS" — the feedback loop that makes delegation more robust over time. - publish.sh: on @pm's RETRO marker, open a "retro: issue #N" issue pointing at the issue + its PR (state=all resolve, works after merge) and trigger @senior on it (has gitea-api to read both threads). The retro produces a LEARNINGS.md PR through the NORMAL choreography (senior → pm → qa), so retros are reviewed like any change. Strip the RETRO marker from visible replies. - publish.sh: bounce counter now counts only @qa-authored comments matching the exact trigger template — on PR #84 it jumped 1/3 → 3/3 because a qa review QUOTED our own "(fix attempt …)" template from the diff, halving the fix budget. Template + regex pinned together with a sync note. - README: document the retro loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
129 lines
8.5 KiB
Markdown
129 lines
8.5 KiB
Markdown
# agents
|
|
|
|
Shared **AI dev-team** workflow for Gitea Actions, reusable across repos. It gives any repo the
|
|
`@pm` / `@junior` / `@senior` / `@lead` / `@qa` agents driven from issues and comments.
|
|
|
|
## Agents
|
|
|
|
| Agent | Model | Vision | Mode | Skills | Role |
|
|
|-------|-------|:------:|------|--------|------|
|
|
| `@pm` | `ollama-cloud/gemma4: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`. |
|
|
| `@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. |
|
|
| `@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. |
|
|
|
|
The registry `.gitea/workflows/scripts/agents.json` is the source of truth for this mapping — if you
|
|
change a model or an agent's skills there, update this table too. (Repo-specific skills, e.g. a
|
|
deploy-host SSH skill, live in the consuming repo under `.gitea/agent-skills/` — not in this table.)
|
|
|
|
## How a task flows
|
|
|
|
`@pm` orchestrates from the **issue thread**; the review happens on the **PR**; `@pm` never reads the PR
|
|
(keeps its context small) and `@qa` never merges.
|
|
|
|
1. **Issue opened** → `@pm` plans and names a dev, then asks the creator *"ready? reply yes"*
|
|
(with the `autopilot` label it skips the question and delegates immediately).
|
|
2. **Dev builds** on `ai/issue-N`, a PR opens automatically, and the dev pings `@pm` on the issue.
|
|
3. `@pm` hands the PR to **`@qa`**.
|
|
4. `@qa` reviews **on the PR** — either recommendations + `BOUNCE: @dev` (dev fixes → `@qa`
|
|
re-verifies, direct loop, max 3 rounds) or `APPROVE`.
|
|
5. On approval `@qa` posts the verdict **on the issue** → `@pm` tells the creator *"ready to merge"*
|
|
and a **human merges** — or, with the `autopilot` label, `@pm` merges and closes the issue itself.
|
|
|
|
`@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
|
|
`<available_skills>` list, and the full `SKILL.md` body (curl/API how-to) is fetched *only* when
|
|
the agent calls the `skill` tool — it is never baked into any system prompt. On top of that, each
|
|
agent's `skills` list in the registry drives an OpenCode `permission.skill` block that **denies all
|
|
skills by default and allows only the listed ones**. A denied skill is hidden entirely (its name and
|
|
description are omitted), so e.g. `@junior` never sees `gitea-api` — it just knows from the roster
|
|
that `@senior`/`@lead` can reach the Gitea API and asks them to. This keeps the "how it's done"
|
|
detail out of agents that shouldn't act on it while still letting them know the capability exists.
|
|
|
|
## Use it in a repo
|
|
|
|
**The standard caller is one file, identical in every repo.** Copy this repo's own
|
|
[`.gitea/workflows/ai-agent.yml`](.gitea/workflows/ai-agent.yml) verbatim into the consuming repo —
|
|
it is the source of truth, and `agents` itself uses the same file:
|
|
|
|
```yaml
|
|
name: ai-agent
|
|
run-name: "ai-agent · #${{ github.event.issue.number }}" # quotes required: bare # starts a YAML comment
|
|
# Standard caller for the shared AI-agent workflow (gitea/agents). Copy this file VERBATIM into
|
|
# any repo that should get the agents — it is identical in every repo. All logic + scripts live in
|
|
# agents/.gitea/workflows/; scripts are fetched from @main at run time. The `jobs.agent` wrapper is
|
|
# required: a reusable (workflow_call) workflow can only be invoked from a caller job, not top-level.
|
|
# `run-name` titles each run by the triggering issue (e.g. "ai-agent · #42") in the Actions list.
|
|
on:
|
|
issue_comment:
|
|
types: [created]
|
|
issues:
|
|
types: [opened]
|
|
jobs:
|
|
agent:
|
|
uses: gitea/agents/.gitea/workflows/agent.yml@main
|
|
secrets: inherit
|
|
```
|
|
|
|
That's the whole per-repo footprint, and it's the minimum a caller can be: the `on:` triggers must
|
|
live in each repo (a reusable workflow can't declare its callers' triggers) and the `jobs.agent`
|
|
wrapper is mandatory for `workflow_call`. Everything else (agent registry, routing, delegation,
|
|
reactions, PR/issue plumbing) lives here in `agent.yml`.
|
|
|
|
## Repo layout
|
|
|
|
`agent.yml` is kept thin: each step's shell lives in its own file under
|
|
`.gitea/workflows/scripts/` (`route.sh`, `install-opencode.sh`, `skill-node1-ssh.sh`,
|
|
`skill-gitea-api.sh`, `fetch-images.sh`, `fetch-thread.sh`, `run-agent.sh`,
|
|
`build-activity-log.sh`, `publish.sh`), invoked as `bash "$SCRIPTS/<name>.sh"`.
|
|
|
|
Because this is a **reusable** workflow (`workflow_call`), a caller run checks out the *caller's*
|
|
repo, not this one — so those script files aren't on disk by default. `agent.yml` therefore checks
|
|
this repo out into `.agents-workflow/` (pinned to `@main`, matching the caller's `uses: …@main`) and
|
|
points `$SCRIPTS` at it. Keep the workflow and its scripts moving together on `main`.
|
|
|
|
## Required secrets (per repo, or org-level for all)
|
|
|
|
| Secret | For |
|
|
|--------|-----|
|
|
| `ANTHROPIC_API_KEY` | `@lead` (and `@pm`/`@senior`/`@qa` if on Claude) |
|
|
| `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_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`. |
|
|
|
|
Each agent authenticates as **itself**: the Run-agent step selects that agent's `TOKEN_*` into
|
|
`SELF_TOKEN` (never another agent's), and `publish.sh` uses the same token for the trigger comments
|
|
that drive the flow (delegation, `@qa` hand-offs, bounces) and for `@pm`'s autopilot merge — the two
|
|
things the built-in `GITEA_TOKEN` can't do (it won't start
|
|
new runs, and a merge under it won't fire downstream deploys). So **every consuming repo must carry the
|
|
per-agent `TOKEN_*` secrets** (org-level for `gitea/*`, user-level for `ffaerber/*`); there is no shared
|
|
fallback token. `GITEA_TOKEN` is auto-provided (used for reads). Tip: set the `TOKEN_*` once at the
|
|
**org / user** level so every repo inherits them via `secrets: inherit`.
|
|
|
|
## Also add to each consuming repo
|
|
|
|
- **`AGENTS.md`** — the repo's own conventions (copy `AGENTS.template.md` from here and adapt). The
|
|
agent reads the *caller* repo's `AGENTS.md`, so each repo can differ.
|
|
- The bot users (`pm`,`senior`,…) as **collaborators** (needed on private repos, and enables
|
|
`@name` autocomplete).
|
|
|
|
## Maintaining
|
|
|
|
Change agent behavior once, here. Callers pin `@main` (or pin a tag for stability). History is the
|
|
changelog — see `git log`.
|