Skip to content

Issue Triage Agent (Agentic) #27

Issue Triage Agent (Agentic)

Issue Triage Agent (Agentic) #27

name: Issue Triage Agent (Agentic)
on:
issues:
types: [opened, edited]
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
inputs:
mode:
description: 'Run mode: single or all'
required: true
default: 'all'
type: choice
options:
- all
- single
force:
description: 'Re-triage issues that already have a triage label (debug only)'
required: false
default: false
type: boolean
permissions:
contents: read
issues: write
pull-requests: write
models: read # required for the GitHub Models inference API
jobs:
triage:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install -r requirements.txt || true
gh --version
# ─────────────────────────────────────────────────────────────
# SINGLE-ISSUE PATH (triggered by issue opened/edited)
#
# We do NOT inline ${{ github.event.issue.body }} into a heredoc —
# backticks, quotes, and $-signs in the body would otherwise mangle
# the script. Instead we fetch the issue via `gh api` so it round-trips
# cleanly through JSON.
# ─────────────────────────────────────────────────────────────
- name: Fetch issue payload (single)
if: github.event_name == 'issues'
run: |
echo "📥 Fetching issue #${{ github.event.issue.number }} via gh api"
gh api "repos/${GITHUB_REPOSITORY}/issues/${{ github.event.issue.number }}" \
--jq '{number: .number, title: .title, body: .body}' \
> /tmp/issue.json
echo "── Issue payload (sanitized preview) ──"
jq '{number, title, body_length: (.body | length)}' /tmp/issue.json
# Wrap into a list so triage_batch.py can consume it uniformly
jq '[.]' /tmp/issue.json > /tmp/issues.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Log full body (single)
if: github.event_name == 'issues'
run: |
echo "================================================================================"
echo "� FULL ISSUE BODY (as the AI will see it)"
echo "================================================================================"
jq -r '.body' /tmp/issue.json
echo "================================================================================"
- name: Pre-label as triaged (single)
if: github.event_name == 'issues'
run: |
gh label create "triaged" --repo "$GITHUB_REPOSITORY" 2>/dev/null || true
gh issue edit ${{ github.event.issue.number }} \
--add-label "triaged" --repo "$GITHUB_REPOSITORY"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
# ─────────────────────────────────────────────────────────────
# AI-DRIVEN TRIAGE — the LLM reads the body and decides everything:
# classification, the comment to post, and (for auto_fix) the literal
# current_text / desired_text to substitute in the docs.
# triage_batch.py applies the AI's decision (label + comment + PR).
# ─────────────────────────────────────────────────────────────
- name: AI triage + (optional) auto-PR (single)
if: github.event_name == 'issues'
run: |
echo "🤖 Running AI triage via GitHub Models on issue #${{ github.event.issue.number }}"
python3 triage_batch.py /tmp/issues.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LEARN_PR_TOKEN: ${{ secrets.LEARN_PR_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
AI_MODEL: openai/gpt-4o-mini
# Set AI_DEBUG=1 to dump the full prompt + raw model response.
AI_DEBUG: '1'
# Single-issue (event-driven) runs always reprocess — a human
# just opened/edited the issue and wants fresh triage.
TRIAGE_MODE: single
# ─────────────────────────────────────────────────────────────
# BATCH PATH (scheduled OR workflow_dispatch with mode=all)
# ─────────────────────────────────────────────────────────────
- name: Batch triage (scheduled / manual)
if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.mode == 'all')
run: |
echo "🔄 BATCH TRIAGE starting..."
# We include `labels` in the JSON so triage_batch.py can skip
# issues we've already handled (anything carrying one of the
# TRIAGE_LABELS — triaged / auto-fix / needs-review / etc.).
gh issue list --state open \
--json number,title,body,labels --limit 50 \
--repo "$GITHUB_REPOSITORY" > /tmp/issues.json
echo "📋 Fetched $(jq length /tmp/issues.json) open issues"
python3 triage_batch.py /tmp/issues.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LEARN_PR_TOKEN: ${{ secrets.LEARN_PR_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
AI_MODEL: openai/gpt-4o-mini
TRIAGE_MODE: batch
# Only re-triage already-labeled issues when the user explicitly
# asks for it via the workflow_dispatch "force" input.
FORCE_RETRIAGE: ${{ github.event.inputs.force == 'true' && '1' || '' }}