Skip to content

Commit 0dbdde8

Browse files
Ambient Code Botclaude
andcommitted
feat: split triage and pr-fixer into separate GHA workflows
- triage.yml: daily cron (8am UTC weekdays), discovers untriaged Jira/GH issues, creates Implement sessions (max 5/cycle) - pr-fixer.yml: 30 min cron, manages all ai-managed PRs with session reuse, change detection, and circuit breaker - fix-single: triggered only by @ambient-fix comment - fix-batch: scheduled + manual dispatch - Both use ambient-action@v0.0.3 with inactivity timeout (60s) - Unified label: ai-managed (was agent-managed) - Removed old matrix-based batch pattern - Skip Jira gracefully if MCP tool unavailable Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 9b1d8b4 commit 0dbdde8

2 files changed

Lines changed: 195 additions & 123 deletions

File tree

.github/workflows/pr-fixer.yml

Lines changed: 112 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
name: PR Fixer
22

33
on:
4-
# Immediate: when a PR gets the agent-managed label
5-
pull_request:
6-
types: [labeled]
7-
84
# Immediate: when someone comments @ambient-fix on a PR
95
issue_comment:
106
types: [created]
117

12-
# Cadence: every 4 hours including overnight
8+
# Cadence: every 30 minutes on weekdays
139
schedule:
14-
- cron: '0 */4 * * 1-5'
10+
- cron: '*/30 * * * 1-5'
1511

16-
# Manual: for one-off fixes
12+
# Manual: for batch run
1713
workflow_dispatch:
18-
inputs:
19-
pr_number:
20-
description: 'PR number to fix'
21-
required: true
22-
type: number
2314

2415
permissions:
2516
contents: read
@@ -29,29 +20,18 @@ jobs:
2920
# -- Single PR: triggered by label, comment, or manual dispatch --
3021
fix-single:
3122
if: >-
32-
(github.event_name == 'pull_request'
33-
&& github.event.label.name == 'agent-managed'
34-
&& !github.event.pull_request.head.repo.fork)
35-
|| github.event_name == 'workflow_dispatch'
36-
|| (github.event_name == 'issue_comment'
37-
&& github.event.issue.pull_request
38-
&& contains(github.event.comment.body, '@ambient-fix')
39-
&& (github.event.comment.author_association == 'MEMBER'
40-
|| github.event.comment.author_association == 'OWNER'
41-
|| github.event.comment.author_association == 'COLLABORATOR'))
23+
github.event_name == 'issue_comment'
24+
&& github.event.issue.pull_request
25+
&& contains(github.event.comment.body, '@ambient-fix')
26+
&& (github.event.comment.author_association == 'MEMBER'
27+
|| github.event.comment.author_association == 'OWNER'
28+
|| github.event.comment.author_association == 'COLLABORATOR')
4229
runs-on: ubuntu-latest
4330
timeout-minutes: 30
4431
steps:
4532
- name: Resolve PR number
4633
id: pr
47-
run: |
48-
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
49-
echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT
50-
elif [ "${{ github.event_name }}" = "issue_comment" ]; then
51-
echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
52-
else
53-
echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
54-
fi
34+
run: echo "number=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
5535

5636
- name: Check PR is not a fork (issue_comment)
5737
if: github.event_name == 'issue_comment'
@@ -67,26 +47,53 @@ jobs:
6747
echo "skip=false" >> $GITHUB_OUTPUT
6848
fi
6949
50+
- name: Check for existing session
51+
if: steps.fork_check.outputs.skip != 'true'
52+
id: existing
53+
env:
54+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55+
run: |
56+
# Read PR body and extract session_id from frontmatter
57+
BODY=$(gh pr view ${{ steps.pr.outputs.number }} --repo "${{ github.repository }}" --json body --jq '.body')
58+
SESSION_ID=$(echo "$BODY" | grep -oP 'acp:session_id=\K[^ ]+' | head -1 || echo "")
59+
SOURCE=$(echo "$BODY" | grep -oP 'source=\K[^ ]+' | head -1 || echo "")
60+
echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
61+
echo "source=$SOURCE" >> $GITHUB_OUTPUT
62+
if [ -n "$SESSION_ID" ]; then
63+
echo "Found existing session: $SESSION_ID"
64+
else
65+
echo "No existing session found — will create new one"
66+
fi
67+
7068
- name: Fix PR
7169
if: steps.fork_check.outputs.skip != 'true'
7270
id: session
73-
uses: ambient-code/ambient-action@v0.0.2
71+
uses: ambient-code/ambient-action@v0.0.3
7472
with:
7573
api-url: ${{ secrets.AMBIENT_API_URL }}
7674
api-token: ${{ secrets.AMBIENT_BOT_TOKEN }}
7775
project: ${{ secrets.AMBIENT_PROJECT }}
78-
prompt: >-
79-
Fix PR #${{ steps.pr.outputs.number }} in https://github.com/${{ github.repository }}.
80-
Fetch all PR data, rebase to resolve conflicts, evaluate reviewer
81-
comments (fix valid issues, respond to invalid ones), run lints
82-
and tests, and push the fixes.
76+
prompt: |
77+
You are maintaining open pull request #${{ steps.pr.outputs.number }} in https://github.com/${{ github.repository }}.
78+
79+
## Instructions
80+
81+
1. Check out the PR branch.
82+
2. Assess the current state:
83+
- Are there merge conflicts? Resolve them.
84+
- Is CI failing? Read the logs and fix the failures.
85+
- Are there review comments (human or bot like CodeRabbit)? Address each comment.
86+
3. Push fixes to the PR branch.
87+
4. Ensure the PR body contains this frontmatter as the first line
88+
(read your session ID from the AGENTIC_SESSION_NAME environment variable):
89+
<!-- acp:session_id=$AGENTIC_SESSION_NAME source=${{ steps.existing.outputs.source || 'unknown' }} last_action=<ISO8601_NOW> retry_count=0 -->
90+
5. Do not merge. Do not close. Do not force-push.
91+
6. If the PR is fundamentally broken beyond repair, add a comment explaining the situation and stop.
8392
repos: >-
8493
[{"url": "https://github.com/${{ github.repository }}", "branch": "main"}]
85-
workflow: >-
86-
{"gitUrl": "https://github.com/ambient-code/workflows", "branch": "main", "path": "internal-workflows/pr-fixer"}
87-
model: claude-sonnet-4-5
94+
model: claude-opus-4-6
8895
wait: 'true'
89-
timeout: '25'
96+
timeout: '60'
9097

9198
- name: Session summary
9299
if: always() && steps.fork_check.outputs.skip != 'true'
@@ -105,106 +112,88 @@ jobs:
105112
echo "- **Status**: Failed to create session" >> $GITHUB_STEP_SUMMARY
106113
fi
107114
108-
# -- Batch: scheduled cadence for all agent-managed PRs --
115+
# -- Batch: scheduled cadence for all ai-managed PRs --
109116
fix-batch:
110-
if: github.event_name == 'schedule'
111-
runs-on: ubuntu-latest
112-
timeout-minutes: 10
113-
outputs:
114-
pr_numbers: ${{ steps.find.outputs.pr_numbers }}
115-
steps:
116-
- name: Find agent-managed PRs that need work
117-
id: find
118-
env:
119-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
120-
run: |
121-
# Get all open PRs with agent-managed label, including last committer
122-
PRS=$(gh pr list --repo "${{ github.repository }}" \
123-
--label "agent-managed" \
124-
--state open \
125-
--json number,updatedAt,isCrossRepository \
126-
--limit 20)
127-
128-
# Filter: updated in the last 4 hours, not a fork,
129-
# and last update wasn't by the fixer bot itself
130-
NEEDS_WORK=$(echo "$PRS" | python3 -c "
131-
import json, sys
132-
from datetime import datetime, timezone, timedelta
133-
134-
prs = json.load(sys.stdin)
135-
cutoff = datetime.now(timezone.utc) - timedelta(hours=4)
136-
active = []
137-
for pr in prs:
138-
# Skip fork PRs
139-
if pr.get('isCrossRepository', False):
140-
continue
141-
updated = pr.get('updatedAt', '')
142-
if updated:
143-
try:
144-
dt = datetime.fromisoformat(updated.replace('Z', '+00:00'))
145-
if dt > cutoff:
146-
active.append(pr['number'])
147-
except Exception:
148-
active.append(pr['number'])
149-
print(json.dumps(active))
150-
")
151-
152-
echo "pr_numbers=$NEEDS_WORK" >> $GITHUB_OUTPUT
153-
COUNT=$(echo "$NEEDS_WORK" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))")
154-
echo "Found $COUNT agent-managed PRs with recent activity"
155-
156-
fix-each:
157-
needs: fix-batch
158-
if: needs.fix-batch.outputs.pr_numbers != '[]'
117+
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
159118
runs-on: ubuntu-latest
160-
timeout-minutes: 30
161-
strategy:
162-
matrix:
163-
pr_number: ${{ fromJson(needs.fix-batch.outputs.pr_numbers) }}
119+
timeout-minutes: 45
164120
steps:
165-
- name: Skip if last update was by the fixer
166-
id: churn_check
167-
env:
168-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
169-
run: |
170-
# Check if the most recent comment is from the fixer bot
171-
LAST_AUTHOR=$(gh api "repos/${{ github.repository }}/issues/${{ matrix.pr_number }}/comments" \
172-
--jq 'last | .body // ""' 2>/dev/null || echo "")
173-
if echo "$LAST_AUTHOR" | grep -q '<!-- pr-fixer-bot -->'; then
174-
echo "Last activity was fixer bot — skipping"
175-
echo "skip=true" >> $GITHUB_OUTPUT
176-
else
177-
echo "skip=false" >> $GITHUB_OUTPUT
178-
fi
179-
180-
- name: Fix PR #${{ matrix.pr_number }}
181-
if: steps.churn_check.outputs.skip != 'true'
121+
- name: Run PR fixer orchestrator
182122
id: session
183-
uses: ambient-code/ambient-action@v0.0.2
123+
uses: ambient-code/ambient-action@v0.0.3
184124
with:
185125
api-url: ${{ secrets.AMBIENT_API_URL }}
186126
api-token: ${{ secrets.AMBIENT_BOT_TOKEN }}
187127
project: ${{ secrets.AMBIENT_PROJECT }}
188-
prompt: >-
189-
Fix PR #${{ matrix.pr_number }} in https://github.com/${{ github.repository }}.
190-
Fetch all PR data, rebase to resolve conflicts, evaluate reviewer
191-
comments (fix valid issues, respond to invalid ones), run lints
192-
and tests, and push the fixes.
128+
prompt: |
129+
You are a PR fixer orchestrator. Manage all open ai-managed PRs.
130+
131+
## Find PRs
132+
133+
Run: gh pr list --repo ${{ github.repository }} --state open --label ai-managed --search "draft:false"
134+
135+
For each PR:
136+
137+
## 1. Read frontmatter
138+
Parse <!-- acp:session_id=<ID> source=<KEY> last_action=<TIMESTAMP> retry_count=<N> --> from the PR body.
139+
If no frontmatter: create a new Fix PR session, update the PR body with frontmatter. Continue.
140+
141+
## 2. Circuit breaker
142+
If retry_count >= 3: comment "AI was unable to resolve after 3 attempts. Needs human attention.",
143+
add ai-needs-human label, remove ai-managed label. Skip.
144+
145+
## 3. Check for changes since last_action
146+
Ignore commits authored by the bot. Only look for:
147+
- New commits by someone other than the bot
148+
- New or updated review comments
149+
- New CI failures
150+
- Merge conflicts from base branch changes
151+
152+
If nothing changed → skip entirely.
153+
154+
## 4. Something changed — act
155+
- CI failing → send message to existing session with CI logs
156+
- New review comments → send message with the comments
157+
- Merge conflicts → send message to rebase
158+
- New external commits → send message to review and ensure CI passes
159+
160+
## 5. Session management
161+
Before sending a message, check session status:
162+
- Running → send the message
163+
- Stopped → restart (reuse), then send
164+
- Not found → create new session with this prompt:
165+
"You are maintaining an open pull request.
166+
PR: <URL> Source issue: <KEY> (if known)
167+
1. Check out the PR branch.
168+
2. Resolve merge conflicts, fix CI failures, address review comments.
169+
3. Push fixes. Do not merge/close/force-push.
170+
4. Write frontmatter: <!-- acp:session_id=$AGENTIC_SESSION_NAME source=<KEY> last_action=<NOW> retry_count=0 -->
171+
5. If broken beyond repair, comment and stop."
172+
173+
After sending: increment retry_count and update last_action in frontmatter.
174+
175+
## Rules
176+
- No-op if nothing changed. Do not interact with the session.
177+
- Use real ACP session IDs from AGENTIC_SESSION_NAME env var.
178+
- Send messages to existing sessions, don't recreate.
179+
- Ignore bot's own commits.
180+
- Do not merge PRs.
181+
182+
All child sessions use model claude-opus-4-6.
193183
repos: >-
194184
[{"url": "https://github.com/${{ github.repository }}", "branch": "main"}]
195-
workflow: >-
196-
{"gitUrl": "https://github.com/ambient-code/workflows", "branch": "main", "path": "internal-workflows/pr-fixer"}
197-
model: claude-sonnet-4-5
198-
wait: 'false'
185+
model: claude-opus-4-6
186+
wait: 'true'
187+
timeout: '60'
199188

200189
- name: Session summary
201-
if: always() && steps.churn_check.outputs.skip != 'true'
190+
if: always()
202191
env:
203192
SESSION_NAME: ${{ steps.session.outputs.session-name }}
204193
SESSION_UID: ${{ steps.session.outputs.session-uid }}
205194
SESSION_PHASE: ${{ steps.session.outputs.session-phase }}
206195
run: |
207-
echo "### PR Fixer — #${{ matrix.pr_number }}" >> $GITHUB_STEP_SUMMARY
196+
echo "### PR Fixer — Batch" >> $GITHUB_STEP_SUMMARY
208197
echo "" >> $GITHUB_STEP_SUMMARY
209198
if [ -n "$SESSION_NAME" ]; then
210199
echo "- **Session**: \`$SESSION_NAME\`" >> $GITHUB_STEP_SUMMARY

.github/workflows/triage.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: Issue Triage
2+
3+
on:
4+
# Daily: weekdays at 8am UTC
5+
schedule:
6+
- cron: '0 8 * * 1-5'
7+
8+
# Manual: for one-off triage runs
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
issues: write
14+
15+
jobs:
16+
triage:
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 45
19+
steps:
20+
- name: Run triage orchestrator
21+
id: session
22+
uses: ambient-code/ambient-action@v0.0.3
23+
with:
24+
api-url: ${{ secrets.AMBIENT_API_URL }}
25+
api-token: ${{ secrets.AMBIENT_BOT_TOKEN }}
26+
project: ${{ secrets.AMBIENT_PROJECT }}
27+
prompt: |
28+
You are a triage orchestrator. Find untriaged issues and create sessions to fix them.
29+
30+
## Find untriaged items (most recent first, max 5 per cycle)
31+
32+
Query these sources:
33+
- Jira: project = RHOAIENG AND "Team[Team]" = ec74d716-af36-4b3c-950f-f79213d08f71-1917 AND labels NOT IN (ai-triaged) AND type IN (Bug, Story, Task) AND status IN (New, Backlog) ORDER BY created DESC
34+
- GitHub Issues: gh issue list --repo ${{ github.repository }} --state open --search "-label:ai-triaged sort:created-desc"
35+
36+
If the Jira MCP tool is not available, skip Jira and only triage GitHub issues.
37+
38+
Do NOT auto-triage external PRs.
39+
40+
## For each item (up to 5 this cycle)
41+
42+
1. Check if an open non-draft PR with ai-managed label already references this issue. If so, skip.
43+
2. If the issue is unclear, duplicated, or not actionable: comment why, add ai-triaged label, move on.
44+
3. If actionable: create a child session (model: claude-opus-4-6) to investigate and fix it.
45+
The child session prompt should be:
46+
"You are investigating and fixing an issue.
47+
Source: <KEY> — <TITLE>
48+
URL: <URL>
49+
Context: <DESCRIPTION>
50+
Instructions:
51+
1. Read the issue and understand the problem.
52+
2. Explore the codebase to find the relevant code.
53+
3. Implement a fix. Write tests if the area has existing test coverage.
54+
4. Create a PR with a clear description. Include this frontmatter as the first line of the PR body
55+
(read your session ID from the AGENTIC_SESSION_NAME environment variable):
56+
<!-- acp:session_id=$AGENTIC_SESSION_NAME source=<KEY> last_action=<NOW> retry_count=0 -->
57+
5. Add the ai-managed label to the PR.
58+
6. Ensure CI passes. If it fails, investigate and fix.
59+
7. Do not merge. Leave the PR open for human review."
60+
4. Add ai-triaged label AFTER confirming the child session was created.
61+
5. Comment on the issue linking to the child session.
62+
repos: >-
63+
[{"url": "https://github.com/${{ github.repository }}", "branch": "main"}]
64+
model: claude-opus-4-6
65+
wait: 'true'
66+
timeout: '60'
67+
68+
- name: Session summary
69+
if: always()
70+
env:
71+
SESSION_NAME: ${{ steps.session.outputs.session-name }}
72+
SESSION_UID: ${{ steps.session.outputs.session-uid }}
73+
SESSION_PHASE: ${{ steps.session.outputs.session-phase }}
74+
run: |
75+
echo "### Issue Triage" >> $GITHUB_STEP_SUMMARY
76+
echo "" >> $GITHUB_STEP_SUMMARY
77+
if [ -n "$SESSION_NAME" ]; then
78+
echo "- **Session**: \`$SESSION_NAME\`" >> $GITHUB_STEP_SUMMARY
79+
echo "- **UID**: \`$SESSION_UID\`" >> $GITHUB_STEP_SUMMARY
80+
echo "- **Phase**: \`$SESSION_PHASE\`" >> $GITHUB_STEP_SUMMARY
81+
else
82+
echo "- **Status**: Failed to create session" >> $GITHUB_STEP_SUMMARY
83+
fi

0 commit comments

Comments
 (0)