Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ node_modules/
*.log
.env
.env.local

# gh aw compile artifacts in catalog sources (local dev only)
catalog/**/*.lock.yml
22 changes: 19 additions & 3 deletions catalog/agent-team/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# agent-team

A four-workflow pattern for a spec → plan → implement → review pipeline on a single GitHub issue. Each role is a separate gh-aw workflow; they coordinate by **dispatching the next workflow** via gh-aw's `dispatch-workflow` safe-output, passing typed inputs (issue number, iteration counter, optional PR number).
A five-workflow pattern for a spec → plan → implement → review pipeline on a single GitHub issue, plus a sweep that keeps draft PRs rebased onto `main`. Each role is a separate gh-aw workflow; they coordinate by **dispatching the next workflow** via gh-aw's `dispatch-workflow` safe-output, passing typed inputs (issue number, iteration counter, optional PR number).

> **Status**: reference pattern. Templates only — `.lock.yml` files are generated when you install into a target repo.

Expand Down Expand Up @@ -44,10 +44,24 @@ Each agent finishes its work by **emitting a `dispatch-workflow` safe-output** n
issue_number, pr_number,
iteration = iteration + 1
)

(Separately, on a 6-hour cron)
┌─────────────┐ dispatch (issue_number, pr_number, mode=rebase)
│ sweep-agent │─────────────────────────► implementer-agent (for each stale PR)
└─────────────┘
```

`state:*` labels (`plan-needed`, `impl-needed`, `review-needed`, `done`, `blocked`) are **cosmetic breadcrumbs for humans** — they let the GitHub UI show pipeline progress at a glance. They do **not** drive control flow; the `dispatch-workflow` safe-outputs do.

## Rebase handling

Draft PRs drift out of date as `main` advances. Two mechanisms keep them current, no human action required:

1. **Rebase at start of every implementer run** — `impl` mode begins with `git fetch origin main && git rebase origin/main`. Catches drift within the pipeline (initial impl, kickback cycles).
2. **Scheduled sweep** — `sweep-agent.md` runs every 6 hours (and on-demand via `workflow_dispatch`). It lists open `agent-team:pr` draft PRs, checks each for `main`-ancestry, and dispatches the implementer in `rebase` mode for any that are behind. Catches the common "PR sat waiting for human merge, main moved" case.

Both paths share the same escalation rule: mechanical conflicts resolve silently; semantic conflicts (overlapping logic, tests fail after resolve) escalate via `state:blocked` with a targeted comment. The human sees the PR only when it's ready to merge or when their judgment is needed.

## The comment contract

Agents communicate their work product via fenced HTML-comment blocks, which downstream agents grep out of the issue body + comments. Never rely on prose ordering.
Expand All @@ -69,6 +83,7 @@ Sections: `spec`, `plan`, `review`. Each carries the `iteration` at the time it
| `planner-agent.md` | `workflow_dispatch` (issue_number, iteration) | `implementer-agent` (issue_number, iteration) |
| `implementer-agent.md` | `workflow_dispatch` (issue_number, iteration, pr_number?) | `reviewer-agent` (issue_number, pr_number, iteration) |
| `reviewer-agent.md` | `workflow_dispatch` (pr_number, issue_number, iteration) | `implementer-agent` on kickback (iteration+1), else nothing |
| `sweep-agent.md` | `schedule` (every 6h) + `workflow_dispatch` | `implementer-agent` in `rebase` mode, per stale PR |

## Install

Expand All @@ -78,7 +93,7 @@ Use the bundled skill — it's the supported path:
/install-agent-team
```

One flow installs all four workflows, wires auth once, applies the OAuth tweak to every lockfile, and creates the seven labels. See [`skills/install-agent-team/SKILL.md`](../../skills/install-agent-team/SKILL.md).
One flow installs all five workflows, wires auth once, applies the OAuth tweak to every lockfile, and creates the eight labels. See [`skills/install-agent-team/SKILL.md`](../../skills/install-agent-team/SKILL.md).

<details>
<summary>Manual install (advanced)</summary>
Expand All @@ -88,6 +103,7 @@ gh aw add verkyyi/github-agent-runner/catalog/agent-team/spec-agent.md@main
gh aw add verkyyi/github-agent-runner/catalog/agent-team/planner-agent.md@main
gh aw add verkyyi/github-agent-runner/catalog/agent-team/implementer-agent.md@main
gh aw add verkyyi/github-agent-runner/catalog/agent-team/reviewer-agent.md@main
gh aw add verkyyi/github-agent-runner/catalog/agent-team/sweep-agent.md@main
```

Then apply the OAuth token tweak to each `.lock.yml` per [`skills/install-workflow/auth.md`](../../skills/install-workflow/auth.md), and create the labels (see the skill file for the exact `gh label create` commands).
Expand All @@ -97,7 +113,7 @@ Then apply the OAuth token tweak to each `.lock.yml` per [`skills/install-workfl

- Repo Actions settings: **Read and write** permissions + **Allow GitHub Actions to create and approve pull requests**.
- Either `CLAUDE_CODE_OAUTH_TOKEN` (subscription) or `ANTHROPIC_API_KEY` repo secret.
- Labels (`agent-team`, `state:plan-needed`, `state:impl-needed`, `state:review-needed`, `state:done`, `state:blocked`, `agent-team:reviewed`) — the install skill creates them.
- Labels (`agent-team`, `agent-team:pr`, `state:plan-needed`, `state:impl-needed`, `state:review-needed`, `state:done`, `state:blocked`, `agent-team:reviewed`) — the install skill creates them.

## Kicking off a task

Expand Down
118 changes: 109 additions & 9 deletions catalog/agent-team/implementer-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ on:
required: false
type: string
default: ""
mode:
description: >-
Implementer behavior mode. `impl` (default) runs the normal spec→plan→PR flow and rebases onto main at the start.
`rebase` skips spec/plan and only rebases the existing PR onto main, runs tests, and pushes.
required: false
type: string
default: "impl"

concurrency:
group: agent-team-issue-${{ inputs.issue_number }}
Expand All @@ -41,6 +48,7 @@ network:
- rust
- dotnet
- java
- "context7.com"

checkout:
fetch-depth: 0
Expand All @@ -52,6 +60,12 @@ tools:
bash: true
web-fetch:

mcp-servers:
context7:
command: npx
args: ["-y", "@upstash/context7-mcp"]
allowed: [resolve-library-id, get-library-docs]

safe-outputs:
# Trusted-input pipeline (dispatched by the planner in our own repo).
# Skip the ~1-min threat-detection classifier to save wall-clock per run.
Expand Down Expand Up @@ -89,15 +103,71 @@ Inputs:
- `inputs.issue_number` — the issue you're implementing against.
- `inputs.iteration` — attempt number.
- `inputs.pr_number` — if non-empty, you're being re-invoked after a reviewer kickback and should **push updates to the existing PR branch**, not open a new PR.
- `inputs.mode` — behavior mode; `impl` (default) runs the normal spec→plan→PR flow, `rebase` skips to the Rebase-only mode section.

## Mode dispatch

Check `inputs.mode`:
- `impl` (default) or empty → follow the **Normal path** below.
- `rebase` → follow the **Rebase-only mode** section instead; skip the Normal path entirely.

Any other value → add `state:blocked` to `inputs.issue_number`, post `🛑 agent-team: unknown implementer mode "<value>".` on the issue, stop.

## Iteration guard (do this first)
## Iteration guard (impl mode only)

Skip this section entirely if `inputs.mode == "rebase"` — a rebase is not a review attempt.

If `inputs.iteration` is greater than 3:
- Add `state:blocked` to issue `inputs.issue_number`.
- Post one comment on that issue: `🛑 agent-team: max iterations reached at impl stage.`
- Do **not** dispatch the reviewer.
- Stop.

## Rebase-only mode

Triggered when `inputs.mode == "rebase"`. Purpose: keep an existing PR current with `main` without doing any implementation work. Called by the sweep workflow (and can be invoked manually via `gh workflow run`).

The iteration guard does not apply to this mode — a rebase is not a review attempt.

**Preconditions** (fail fast):
- `inputs.pr_number` must be set. If empty, add `state:blocked` to `inputs.issue_number`, comment `🛑 agent-team / rebase: mode=rebase requires pr_number.`, stop.

**Steps**:

1. Check out the PR branch:
- `gh pr view <inputs.pr_number> --json headRefName,state,isDraft` — confirm the PR is open and draft. Stop silently (post no comment) if any of: PR is closed, merged, or `isDraft: false` (a human promoted it out of draft; they control it from here).
- `git fetch origin <branch> && git checkout <branch>`

2. Fetch `main`:
- `git fetch origin main`
- If `main` is already an ancestor of `HEAD` (`git merge-base --is-ancestor origin/main HEAD`), the PR is current. Stop silently — post no comment.

3. Rebase:
- `git rebase origin/main`.
- On conflicts → follow the "Conflict resolution" section below. Clean rebase or successful mechanical resolve → continue to step 4.

4. Run the project's test command once. Use the same test-command detection as `impl` mode (read `package.json` / `Makefile` / CI files). If no test command is detectable, skip and note that in step 5.
- Tests pass → continue to step 5.
- Tests fail → the rebase itself is already complete, so the CR section's `git rebase --abort` does not apply. Run `git reset --hard ORIG_HEAD` to restore the pre-rebase state, then escalate per "Conflict resolution" escalation format, stop.

5. Push:
- `git push --force-with-lease origin HEAD:<branch>`
- Post one comment on the PR — body:
```
🤖 agent-team / rebase: rebased onto main at <short-sha>.

- Rebase: <clean | resolved N mechanical conflict(s) in <files>>
- Tests: <✅ passed | ⚠ skipped — no test command detected>
```

6. Stop. **Do not dispatch the reviewer.** Rebase mode is terminal.

**Rules for this mode**:
- Never read the spec or plan. This mode addresses no requirements changes.
- Never dispatch downstream. The PR stays in whatever state it was in (`state:review-needed`, `state:done`, etc.) — a rebase doesn't reset review.
- Never touch files beyond what `git rebase` modifies. No spec-driven edits.
- Force-push uses `--force-with-lease` so a concurrent human push isn't clobbered.

## Normal path

1. Fetch the issue (`gh issue view <inputs.issue_number>`). Extract:
Expand All @@ -111,14 +181,21 @@ If `inputs.iteration` is greater than 3:
- If `inputs.pr_number` is empty → create a new branch: `agent-team/issue-<inputs.issue_number>-<short-slug>`.
- If `inputs.pr_number` is set → check out the existing PR's branch (via `gh pr view <pr_number> --json headRefName`) and push updates to it.

3. Implement **only what the plan says** (plus any kickback requested changes). Do not expand scope.
3. **Rebase the branch onto `main` before editing**:
- `git fetch origin main`
- If `inputs.pr_number` is empty and `git merge-base --is-ancestor origin/main HEAD` exits 0, the branch is already current — skip the rebase.
- Otherwise: `git rebase origin/main`.
- **Clean rebase** → proceed.
- **Rebase produces conflicts** → follow the "Conflict resolution" section below. On successful mechanical resolution, proceed with the normal flow. On escalation (unresolvable conflict or test failure), do not dispatch the reviewer.

4. Implement **only what the plan says** (plus any kickback requested changes). Do not expand scope.
- **Trust the plan.** The planner already explored the repo, confirmed file paths exist, and identified the exact edits. Do NOT re-read surrounding files to "understand the codebase" or "check for patterns." Read only the files the plan names under `Files to change`, plus `AGENTS.md` / `CLAUDE.md` / `CONTRIBUTING.md` once for convention reminders.
- **Edit, don't explore.** For each step, make the edit directly. If a file's current content surprises you relative to the plan, stop (see the "plan is wrong" rule below) — do not start investigating.
- **Run tests ONCE at the end**, not after each edit. Find the command by reading `package.json` / `Makefile` / CI files on the first pass; cache it. Commands to look for: `npm test`, `pytest`, `cargo test`, `go test ./...`, `make test`.
- If tests fail due to your changes, fix and re-run (still one additional run, not per-edit). Unrelated infrastructure failures → document under `## Test status`.
- **Budget check**: if this task feels like it needs more than ~5 tool calls for reading or more than 2 test runs, the plan is probably wrong or you're over-exploring. Stop and re-read this section.

4. Produce the PR:
5. Produce the PR:
- **New PR** (first impl attempt): use `create-pull-request`.
- Title: `<short description from spec>` (the workflow adds the `[agent-team] ` prefix).
- Body:
Expand All @@ -129,14 +206,14 @@ If `inputs.iteration` is greater than 3:
- Footer: `🤖 agent-team / implementer`.
- **Kickback update** (pr_number was set): use `push-to-pull-request-branch` to push the fix commits to the existing PR. Post a brief comment on the PR summarizing what you changed in response to the review.

5. Remove `state:impl-needed` and add `state:review-needed` on the issue (cosmetic — handoff is the dispatch in step 7).
6. Remove `state:impl-needed` and add `state:review-needed` on the issue (cosmetic — handoff is the dispatch in step 8).

6. Capture the PR number:
- New PR: the PR number comes from the `create-pull-request` safe output. Use it in step 7.
7. Capture the PR number:
- New PR: the PR number comes from the `create-pull-request` safe output. Use it in step 8.
- Kickback: use `inputs.pr_number` as-is.

7. **Dispatch the reviewer-agent workflow** with:
- `pr_number`: the number from step 6
8. **Dispatch the reviewer-agent workflow** with:
- `pr_number`: the number from step 7
- `issue_number`: passed through from your input
- `iteration`: passed through from your input (do NOT bump)

Expand All @@ -146,4 +223,27 @@ If `inputs.iteration` is greater than 3:
- Never add dependencies that aren't in the plan. If the plan implies one, pick the minimal option and document in PR body.
- If the plan is wrong (contradicts the spec, impossible in this repo): stop, do NOT open a partial PR. Add `state:blocked` on the issue and post a comment explaining what's wrong with the plan. A human will resolve.
- One concern per PR. If the plan isn't scoped that way, that's a planner bug — report via state:blocked + comment.
- The dispatch in step 7 is the real handoff. `state:review-needed` is decorative.
- The dispatch in step 8 is the real handoff. `state:review-needed` is decorative.

## Conflict resolution

When `git rebase origin/main` produces conflicts (either in `impl` mode's rebase-at-start step or in `rebase` mode):

1. Read each conflicted file. Look at the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`).
2. **Resolve only if the two sides edit disjoint concerns** — e.g. one side renames a variable, the other side adds an unrelated function nearby. Keep both changes.
3. **Do not resolve** if either side changed the same logic (e.g. both sides modified the same function body in ways that affect behavior). That's a semantic conflict requiring human judgment.
4. After resolving, `git add <files>` and `git rebase --continue`.
5. After all conflicts are resolved (or none existed), run the project's test command **once**. If tests pass → return to the caller's next step (in `impl` mode, proceed with the normal flow; in `rebase` mode, push and comment). If tests fail → `git rebase --abort` (or `git reset --hard ORIG_HEAD` if already past rebase), escalate via `state:blocked` with the failing test output.

Escalation format (when blocking due to unresolvable conflict or test failure after resolve):
- Add `state:blocked` to `inputs.issue_number`.
- Comment on the PR (or issue, if no PR yet) — body:
```
🛑 agent-team / <impl-or-rebase>: rebase onto main blocked.

**Reason**: <semantic conflict in <files> | tests failed after mechanical resolve>
**Conflicting files**: <list>
**What I tried**: <one sentence>
**Next**: human resolves locally, then removes state:blocked to re-enter the pipeline.
```
- Stop. Do not dispatch downstream.
12 changes: 11 additions & 1 deletion catalog/agent-team/planner-agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,24 @@ permissions:
contents: read
issues: read

network: defaults
network:
allowed:
- defaults
- node
- "context7.com"

tools:
github:
toolsets: [default]
min-integrity: none
bash: true

mcp-servers:
context7:
command: npx
args: ["-y", "@upstash/context7-mcp"]
allowed: [resolve-library-id, get-library-docs]

safe-outputs:
# Trusted-input pipeline (dispatched by the spec-agent in our own repo).
# Skip the ~1-min threat-detection classifier to save wall-clock per run.
Expand Down
86 changes: 86 additions & 0 deletions catalog/agent-team/sweep-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
engine: claude
description: |
Sweep agent for the agent-team pattern. Runs on a schedule and on demand,
enumerates open draft PRs labeled `agent-team:pr`, and dispatches the
implementer in `rebase` mode for any that have fallen behind `main`.
No LLM reasoning on the diffs themselves — it's enumerate + ancestry
check + dispatch.

on:
schedule:
- cron: "17 */6 * * *"
workflow_dispatch: {}

concurrency:
group: agent-team-sweep
cancel-in-progress: false

timeout-minutes: 5

permissions:
contents: read
issues: read
pull-requests: read

network:
allowed:
- defaults

checkout:
fetch-depth: 0

tools:
github:
toolsets: [default]
min-integrity: none
bash: true

safe-outputs:
threat-detection: false
dispatch-workflow:
workflows: [implementer-agent]
max: 20
---

# Sweep Agent

You are the **sweep** for the agent-team pipeline. Your job: find open draft PRs labeled `agent-team:pr` that are behind `main`, and dispatch the implementer in `rebase` mode for each.

## Steps

1. Fetch `main` once and list candidate PRs:
```
git fetch origin main --quiet

gh pr list --label agent-team:pr --state open --draft \
--json number,headRefName,headRefOid,body --limit 50
```

2. For each PR in the list:

a. Derive `issue_number` by parsing `Closes #<N>` from the PR body. If no `Closes #N` marker exists, **skip that PR** (log the skip; do not dispatch).

b. Check if the PR is behind `main`:
```
git merge-base --is-ancestor origin/main <headRefOid>
```
- Exit code `0` → PR is current, skip it.
- Exit code `1` → PR is behind, dispatch (next step).

c. Dispatch the implementer in rebase mode via the `dispatch-workflow` safe-output:
- workflow: `implementer-agent`
- inputs:
- `issue_number`: `<N>` (from step 2a)
- `pr_number`: `<PR number>`
- `iteration`: `"1"` (rebase mode bypasses the iteration guard; any value works)
- `mode`: `"rebase"`

3. After the loop, post no comment. The dispatched runs' logs (visible in the Actions tab, linked from each dispatched workflow run) are the audit trail.

## Rules

- Sweep never edits code, never rebases itself, never dispatches anything except the implementer in `rebase` mode.
- If `gh pr list` returns zero PRs, stop silently — no comment, no dispatch.
- If more than 20 PRs are behind (unusually large), dispatch the first 20 only. The next sweep run (6h later) picks up the rest. Prevents dispatch-workflow cap from erroring out.
- Sweep is re-entrant safe — back-to-back dispatches of the same PR produce at most one actual rebase push. If two sweeps overlap before the first's dispatched rebases complete, the second may re-dispatch; the implementer's rebase-mode ancestry check then exits silently with no push.
Loading
Loading