Felix FaerberandClaude Opus 4.8 06df6320a1 security: isolate agent behavior from the caller repo
The agent runs inside the CALLER repo's checkout and opencode auto-discovers project-level config
from the working tree (opencode.json / .opencode/ for provider/model/permission/mcp/plugins, and
AGENTS.md/CLAUDE.md for instructions). opencode has no flag to disable that discovery, so any caller
repo could swap the model, unlock skills/permissions, register MCP servers or plugins (arbitrary
code execution on the runner, which holds ANTHROPIC_API_KEY/AGENT_TOKEN/TOKEN_*/SSH_PRIV_KEY), or
rewrite the rules the agent follows. The trigger gate only restricts who triggers, not what repo
content defines behavior.

- run-agent.sh: quarantine caller-controlled opencode.json/opencode.jsonc/.opencode/AGENTS.md/CLAUDE.md
  for the duration of each run (moved aside, restored on EXIT via trap). Restore keeps the committed
  tree/PR diff unchanged even when the agent commits mid-run.
- run-agent.sh: dev-agent golden rules now come from scripts/agent-rules.md (platform-authoritative),
  injected into the prompt, instead of "read the caller's AGENTS.md and follow it exactly".
- scripts/agent-rules.md: new repo-agnostic authoritative rules.
- SECURITY.md: documents the isolation model and the requirement that callers pin agent.yml@main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 11:05:46 +03:00

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 — 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 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 <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

Add .gitea/workflows/ai-agent.yml to the consuming repo:

name: ai-agent
on:
  issue_comment:
    types: [created]
  issues:
    types: [opened]
jobs:
  agent:
    uses: ffaerber/agents/.gitea/workflows/agent.yml@main
    secrets: inherit

That's the whole per-repo footprint. All the logic (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)
AGENT_TOKEN PAT (issue/repository/organization/misc read+write, cross-repo) — posts the delegation comment that fires the next agent and powers the gitea-api skill (read/write issues, PRs, comments, labels, and Actions runs/logs across any repo). Do not re-narrow its scopes without also removing the gitea-api skill.
TOKEN_PM,TOKEN_SENIOR,TOKEN_JUNIOR,TOKEN_LEAD,TOKEN_QA optional — post/commit as each agent's own Gitea user (falls back to the bot)

GITEA_TOKEN is auto-provided. Tip: set these once at the org 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.

S
Description
No description provided
Readme
578 KiB
Languages
Shell 100%