From a79b49b55e76ce7e32ffb62f4c86a9e2db4327ad Mon Sep 17 00:00:00 2001 From: lead Date: Sat, 4 Jul 2026 05:25:06 +0000 Subject: [PATCH] =?UTF-8?q?@lead:=20issue=20#22=20=E2=80=94=20per-agent=20?= =?UTF-8?q?skill=20scoping=20via=20permission.skill?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/agent.yml | 38 +++++++++++++++++++++++++++++--------- README.md | 27 +++++++++++++++++++-------- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/.gitea/workflows/agent.yml b/.gitea/workflows/agent.yml index 0c563ff..875eb46 100644 --- a/.gitea/workflows/agent.yml +++ b/.gitea/workflows/agent.yml @@ -52,14 +52,20 @@ jobs: TOKEN_LEAD: ${{ secrets.TOKEN_LEAD }} TOKEN_QA: ${{ secrets.TOKEN_QA }} run: | - # --- agent registry: model + capabilities + mode + role --- + # --- agent registry: model + capabilities + mode + role + skills --- + # `skills` is the allow-list of opencode Skills each agent may load. It scopes the + # `permission.skill` block written into opencode.json below so an agent only 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 , + # so the 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. cat > /tmp/agents.json <<'JSON' { - "pm": {"model":"ollama-cloud/gemma4:cloud","vision":true, "mode":"comment","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", "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", "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", "desc":"Tech lead — the hardest problems, architecture, and final calls."}, - "qa": {"model":"ollama-cloud/minimax-m3:cloud","vision":true, "mode":"comment","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."} + "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. @@ -74,8 +80,10 @@ jobs: model=$(jq -r --arg a "$name" '.[$a].model' /tmp/agents.json) vision=$(jq -r --arg a "$name" '.[$a].vision' /tmp/agents.json) mode=$(jq -r --arg a "$name" '.[$a].mode' /tmp/agents.json) - echo "Routing to @$name (model=$model vision=$vision mode=$mode)" - { echo "name=$name"; echo "model=$model"; echo "vision=$vision"; echo "mode=$mode"; } >> "$GITHUB_OUTPUT" + # Compact JSON array of the skills this agent may load (scopes permission.skill below). + skills=$(jq -c --arg a "$name" '.[$a].skills // []' /tmp/agents.json) + echo "Routing to @$name (model=$model vision=$vision mode=$mode skills=$skills)" + { echo "name=$name"; echo "model=$model"; echo "vision=$vision"; echo "mode=$mode"; echo "skills=$skills"; } >> "$GITHUB_OUTPUT" # Act as the agent's own Gitea user when its token is set; else the built-in bot. case "$name" in @@ -108,6 +116,7 @@ jobs: OLLAMA_URL: ${{ secrets.OLLAMA_URL }} OLLAMA_CLOUD_API_KEY: ${{ secrets.OLLAMA_CLOUD_API_KEY }} NAME: ${{ steps.prep.outputs.name }} + SKILLS: ${{ steps.prep.outputs.skills }} # JSON array of skills this agent may load run: | curl -fsSL https://opencode.ai/install | bash echo "$HOME/.opencode/bin" >> "$GITHUB_PATH" @@ -121,12 +130,23 @@ jobs: npx -y playwright install --with-deps chromium || npx -y playwright install chromium || true ;; esac + # Per-agent skill scoping. Skills are loaded on-demand by opencode: only a skill's one-line + # `description` ever appears in an agent's list, and the full SKILL.md + # body (curl/API how-to) is loaded ONLY when the agent calls the `skill` tool — it is never + # in any system prompt. To also hide the summary from agents that shouldn't use a skill, we + # deny all skills by default and allow only the ones in this agent's registry list. A denied + # skill is hidden entirely (name + description omitted), so e.g. @junior never sees gitea-api + # at all; it just knows from the roster that @senior/@lead can, and asks them. + SKILLS="${SKILLS:-[]}" + PERM=$(jq -nc --argjson s "$SKILLS" ' + {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). - jq -n --argjson mcp "$MCP" --arg url "$OLLAMA_URL" --arg ckey "$OLLAMA_CLOUD_API_KEY" '{ + jq -n --argjson mcp "$MCP" --argjson perm "$PERM" --arg url "$OLLAMA_URL" --arg ckey "$OLLAMA_CLOUD_API_KEY" '{ provider: { 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":{}}} }, + permission: $perm, mcp: $mcp }' > ~/.config/opencode/opencode.json echo "opencode config (secrets masked):"; cat ~/.config/opencode/opencode.json diff --git a/README.md b/README.md index 7021055..6735b10 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,27 @@ Shared **AI dev-team** workflow for Gitea Actions, reusable across repos. It giv ## Agents -| Agent | Model | Vision | Mode | Role | -|-------|-------|:------:|------|------| -| `@pm` | `ollama-cloud/gemma4:cloud` | yes | comment | Product manager — research, plan, ask clarifying questions, and decide which dev should do the work. Comments only; never edits files. | -| `@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 | Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only). | -| `@lead` | `anthropic/claude-opus-4-8` | yes | pr | Tech lead — the hardest problems, architecture, and final calls. | -| `@qa` | `ollama-cloud/minimax-m3:cloud` | yes | comment | 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. | +| Agent | Model | Vision | Mode | Skills | Role | +|-------|-------|:------:|------|--------|------| +| `@pm` | `ollama-cloud/gemma4:cloud` | yes | comment | `gitea-api` | Product manager — research, plan, ask clarifying questions, and decide which dev should do the work. Comments only; never edits files. | +| `@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`, `node1-ssh` | Senior dev — complex, multi-file implementation (GLM-5.2 via Ollama Cloud, text-only). | +| `@lead` | `anthropic/claude-opus-4-8` | yes | pr | `gitea-api`, `node1-ssh` | Tech lead — the hardest problems, architecture, and final calls. | +| `@qa` | `ollama-cloud/minimax-m3:cloud` | yes | comment | `gitea-api` | 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. | `agent.yml`'s agent registry is the source of truth for this mapping — if you change a model -there, update this table too. +or an agent's skills there, update this table too. + +### Per-agent skill scoping + +Skills load **on-demand**: only a skill's one-line `description` ever appears in an agent's +`` 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