chore: stop auto-labeling pull requests

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 964363281
This commit is contained in:
George Weale
2026-08-13 16:40:50 -07:00
committed by Copybara-Service
parent 5bcf5cdd18
commit 4148f852aa
4 changed files with 77 additions and 119 deletions
+11 -10
View File
@@ -15,15 +15,16 @@
name: ADK Pull Request Triaging Agent
on:
# React within seconds of a PR opening/updating so the owner is assigned
# promptly. pull_request_target (not pull_request) is required so the run has
# the base-repo token needed to assign on community fork PRs; this workflow
# only reads PR metadata via the API and never checks out untrusted PR code.
# React within seconds of a PR arriving so the owner is assigned promptly.
# pull_request_target (not pull_request) is required so the run has the
# base-repo token needed to assign on community fork PRs; this workflow only
# reads PR metadata via the API and never checks out untrusted PR code.
#
# Only events that bring in a new PR are listed. Pushes to an open PR and a
# periodic backfill would both re-assign an owner a maintainer had just taken
# off, so removing an assignee has to stay a decision the agent cannot undo.
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review]
schedule:
# Backfill every 6 hours in case an event was missed.
- cron: '0 */6 * * *'
types: [opened, reopened, ready_for_review]
workflow_dispatch:
inputs:
pr_number:
@@ -36,8 +37,8 @@ on:
default: '10'
type: 'string'
# Never let two runs triage the same PR at once (e.g. rapid pushes); the
# scheduled backfill still runs under its own group.
# Never let two runs triage the same PR at once (e.g. a reopen right after an
# open); a manually dispatched batch run gets its own group.
concurrency:
group: pr-triage-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: false
@@ -1,6 +1,6 @@
# ADK Pull Request Triaging Assistant
The ADK Pull Request (PR) Triaging Assistant is a Python-based agent designed to help manage and triage GitHub pull requests for the `google/adk-python` repository. It uses a large language model to analyze new and unlabelled pull requests, recommend appropriate labels, and assign the component owner based on a predefined set of rules.
The ADK Pull Request (PR) Triaging Assistant is a Python-based agent designed to help manage and triage GitHub pull requests for the `google/adk-python` repository. It uses a large language model to work out which component an incoming pull request belongs to, and assigns that component's owner as the assignee. It never labels a pull request; the component it picks is only used to look up the owner.
This agent can be operated in two distinct modes:
@@ -16,7 +16,7 @@ This mode allows you to run the agent locally to review its recommendations in r
### Features
- **Web Interface**: The agent's interactive mode can be rendered in a web browser using the ADK's `adk web` command.
- **User Approval**: In interactive mode, the agent is instructed to ask for your confirmation before applying a label or assigning an owner to a GitHub pull request.
- **User Approval**: In interactive mode, the agent is instructed to ask for your confirmation before assigning an owner to a GitHub pull request.
### Running in Interactive Mode
@@ -36,13 +36,13 @@ For automated, hands-off PR triaging, the agent can be integrated directly into
### Workflow Triggers
The GitHub workflow is configured to run on specific triggers:
The GitHub workflow runs when a PR is `opened`, `reopened`, or marked `ready_for_review`, and on manual dispatch.
- **Pull Request Events**: The workflow executes automatically whenever a new PR is `opened` or an existing one is `reopened` or `edited`.
It deliberately does not run on pushes to an open PR, and there is no periodic backfill. Either one would re-assign an owner that a maintainer had just taken off, so removing an assignee stays a decision the agent cannot undo.
### Automated Labeling
### Automated Assignment
When running as part of the GitHub workflow, the agent operates non-interactively. It identifies and applies the best label and assigns the component owner directly without requiring user approval. This behavior is configured by setting the `INTERACTIVE` environment variable to `0` in the workflow file.
When running as part of the GitHub workflow, the agent operates non-interactively. It assigns the component owner directly without requiring user approval, and skips any PR that already has an assignee. This behavior is configured by setting the `INTERACTIVE` environment variable to `0` in the workflow file.
### Workflow Configuration
@@ -28,23 +28,19 @@ from component_owners import LABEL_TO_OWNER
from google.adk import Agent
import requests
# LABEL_TO_OWNER (component label -> owner GitHub login; the owner becomes the
# PR's assignee) is imported from component_owners and shared verbatim with
# LABEL_TO_OWNER (component -> owner GitHub login; the owner becomes the PR's
# assignee) is imported from component_owners and shared verbatim with
# adk_triaging_agent, so the two can't drift. Keep it in sync with OWNERS.
# Labels the agent may apply, derived from LABEL_TO_OWNER so a newly-owned
# component is allowed automatically (this was a separate list that silently
# fell out of sync and blocked applying the newer labels).
ALLOWED_LABELS = sorted(LABEL_TO_OWNER)
# This agent only reads the map to pick an assignee; the component names are
# never written to the PR as labels.
APPROVAL_INSTRUCTION = (
"Do not ask for user approval for labeling or assigning!"
" If you can't find appropriate labels for the PR, do not label it."
"Do not ask for user approval for assigning!"
" If you can't tell which component the PR belongs to, leave it"
" unassigned."
)
if IS_INTERACTIVE:
APPROVAL_INSTRUCTION = (
"Only label or assign when the user approves the action!"
)
APPROVAL_INSTRUCTION = "Only assign when the user approves the action!"
def get_pull_request_details(pr_number: int) -> dict[str, Any]:
@@ -156,43 +152,8 @@ def get_pull_request_details(pr_number: int) -> dict[str, Any]:
return error_response(str(e))
def add_label_to_pr(pr_number: int, label: str) -> dict[str, Any]:
"""Adds a specified label on a pull request.
Args:
pr_number: the number of the GitHub pull request
label: the label to add
Returns:
The status of this request, with the applied label and response when
successful.
"""
print(f"Attempting to add label '{label}' to PR #{pr_number}")
if label not in ALLOWED_LABELS:
return error_response(
f"Error: Label '{label}' is not an allowed label. Will not apply."
)
# Pull Request is a special issue in GitHub, so we can use issue url for PR.
label_url = (
f"{GITHUB_BASE_URL}/repos/{OWNER}/{REPO}/issues/{pr_number}/labels"
)
label_payload = [label]
try:
response = post_request(label_url, label_payload)
except requests.exceptions.RequestException as e:
return error_response(f"Error: {e}")
return {
"status": "success",
"applied_label": label,
"response": response,
}
def assign_owner_to_pr(pr_number: int, label: str) -> dict[str, Any]:
"""Assign the component owner (the shepherd) to a PR based on its label.
def assign_owner_to_pr(pr_number: int, component: str) -> dict[str, Any]:
"""Assign the component owner (the shepherd) to a PR.
The owner is looked up from `LABEL_TO_OWNER` so the contributor can see who is
shepherding their PR. GitHub only allows assigning users with
@@ -201,14 +162,16 @@ def assign_owner_to_pr(pr_number: int, label: str) -> dict[str, Any]:
Args:
pr_number: the number of the GitHub pull request
label: the component label the PR was triaged into
component: the component the PR belongs to
Returns:
The status of this request, with the assigned owner when successful.
"""
owner = LABEL_TO_OWNER.get(label)
owner = LABEL_TO_OWNER.get(component)
if not owner:
return error_response(f"Error: no owner mapped for label '{label}'.")
return error_response(
f"Error: no owner mapped for component '{component}'."
)
print(f"Attempting to assign owner '{owner}' to PR #{pr_number}")
if not is_assignable(owner):
return {
@@ -233,11 +196,11 @@ def assign_owner_to_pr(pr_number: int, label: str) -> dict[str, Any]:
}
def list_untriaged_pull_requests(pr_count: int) -> dict[str, Any]:
"""List open pull requests that need triaging.
def list_unassigned_pull_requests(pr_count: int) -> dict[str, Any]:
"""List open pull requests that have nobody assigned.
Returns pull requests that need triaging (i.e. do not have google-contributor
label and do not have any allowed triage category labels).
Skips pull requests labeled google-contributor, which are shepherded by their
own author.
Args:
pr_count: number of pull requests to return
@@ -246,7 +209,7 @@ def list_untriaged_pull_requests(pr_count: int) -> dict[str, Any]:
The status of this request, with a list of pull requests when successful.
"""
url = f"{GITHUB_BASE_URL}/search/issues"
query = f"repo:{OWNER}/{REPO} is:open is:pr"
query = f"repo:{OWNER}/{REPO} is:open is:pr no:assignee"
params = {
"q": query,
"sort": "updated",
@@ -261,32 +224,28 @@ def list_untriaged_pull_requests(pr_count: int) -> dict[str, Any]:
return error_response(f"Error: {e}")
issues = response.get("items", [])
triage_labels = set(ALLOWED_LABELS)
untriaged_prs = []
unassigned_prs = []
for pr in issues:
pr_labels = {label["name"] for label in pr.get("labels", [])}
if "google-contributor" in pr_labels:
continue
# If it already has any of the ALLOWED_LABELS, skip it.
if pr_labels & triage_labels:
continue
untriaged_prs.append({
unassigned_prs.append({
"number": pr["number"],
"title": pr["title"],
})
if len(untriaged_prs) >= pr_count:
if len(unassigned_prs) >= pr_count:
break
return {"status": "success", "pull_requests": untriaged_prs}
return {"status": "success", "pull_requests": unassigned_prs}
root_agent = Agent(
model="gemini-3.5-flash",
name="adk_pr_triaging_assistant",
description="Triage ADK pull requests.",
description="Assign component owners to ADK pull requests.",
instruction=f"""
# 1. Identity
You are a Pull Request (PR) triaging bot for the GitHub {REPO} repo with the owner {OWNER}.
@@ -294,55 +253,53 @@ root_agent = Agent(
# 2. Responsibilities
Your core responsibility includes:
- Get the pull request details.
- Add a label to the pull request.
- Assign the component owner (the shepherd) to the pull request.
- Work out which component the pull request belongs to.
- Assign that component's owner (the shepherd) to the pull request.
Never label a pull request. The component you pick is only used to look up
the owner, and is never written to the pull request.
**IMPORTANT: {APPROVAL_INSTRUCTION}**
# 3. Guidelines & Rules
Here are the rules for labeling:
- If the PR is about documentations, label it with "documentation".
- If it's about session, memory, artifacts services, label it with "services"
- If it's about UI/web, label it with "web"
- If it's related to tools, label it with "tools"
- If it's about agent evaluation, then label it with "eval".
- If it's about streaming/live, label it with "live".
- If it's about model support(non-Gemini, like Litellm, Ollama, OpenAI models), label it with "models".
- If it's about tracing, label it with "tracing".
- If it's about authentication or authorization, label it with "auth".
- If it's about BigQuery integration, label it with "bq".
- If it's about ADK CLI commands (e.g. create, deploy, eval) or CLI tools, label it with "cli".
- If it's about third-party integrations (e.g. CrewAI, LangChain, Slack) excluding BigQuery, label it with "integrations".
- If it's about GCP Skills Registry (GCPSkillRegistry), skill prompt models, or dynamic skill toolsets, label it with "skills".
- If it's about workflow agents or workflow execution, label it with "workflow".
- If it's agent orchestration, agent definition, label it with "core".
- If it's about Model Context Protocol (e.g. MCP tool, MCP toolset, MCP session management etc.), label it with "mcp".
- If you can't find an appropriate labels for the PR, follow the previous instruction that starts with "IMPORTANT:".
Here are the rules for picking the component:
- If the PR is about documentations, the component is "documentation".
- If it's about session, memory, artifacts services, the component is "services".
- If it's about UI/web, the component is "web".
- If it's related to tools, the component is "tools".
- If it's about agent evaluation, the component is "eval".
- If it's about streaming/live, the component is "live".
- If it's about model support(non-Gemini, like Litellm, Ollama, OpenAI models), the component is "models".
- If it's about tracing, the component is "tracing".
- If it's about authentication or authorization, the component is "auth".
- If it's about BigQuery integration, the component is "bq".
- If it's about ADK CLI commands (e.g. create, deploy, eval) or CLI tools, the component is "cli".
- If it's about third-party integrations (e.g. CrewAI, LangChain, Slack) excluding BigQuery, the component is "integrations".
- If it's about GCP Skills Registry (GCPSkillRegistry), skill prompt models, or dynamic skill toolsets, the component is "skills".
- If it's about workflow agents or workflow execution, the component is "workflow".
- If it's agent orchestration, agent definition, the component is "core".
- If it's about Model Context Protocol (e.g. MCP tool, MCP toolset, MCP session management etc.), the component is "mcp".
- If you can't find an appropriate component for the PR, follow the previous instruction that starts with "IMPORTANT:".
# 4. Steps
- If you are asked to find pull requests that need triaging, use `list_untriaged_pull_requests` first.
- For each pull request to be triaged:
- If you are asked to find pull requests that need an owner, use `list_unassigned_pull_requests` first.
- For each pull request:
- Call the `get_pull_request_details` tool to get the details of the PR.
- Skip the PR (i.e. do not label) if any of the following is true:
- Skip the PR (i.e. do not assign anyone) if any of the following is true:
- the PR is closed
- the PR is labeled with "google-contributor"
- the PR is already labelled with the above labels (e.g. "documentation", "services", "tools", etc.).
- Recommend or add a label to the PR.
- After you add a component label, assign the component owner (the shepherd) to the PR:
- Call `assign_owner_to_pr` with the same label you applied.
- Skip assignment if the PR already has an assignee.
- If the tool reports the owner is not assignable, just note it.
- the PR already has an assignee
- Work out the component, then call `assign_owner_to_pr` with it.
- If the tool reports the owner is not assignable, just note it.
# 5. Output
Present the following in an easy to read format highlighting PR number and your label.
Present the following in an easy to read format highlighting PR number and the owner you assigned.
- The PR summary in a few sentence
- The label you recommended or added with the justification
- The owner you assigned (or why you did not)
- The owner you assigned with the justification, or why you assigned nobody
""",
tools=[
list_untriaged_pull_requests,
list_unassigned_pull_requests,
get_pull_request_details,
add_label_to_pr,
assign_owner_to_pr,
],
)
@@ -51,9 +51,9 @@ async def main():
f" {pr_count})."
)
prompt = (
f"Please use 'list_untriaged_pull_requests' to find {pr_count} pull"
" requests that need triaging, then triage each one according to your"
" instructions."
f"Please use 'list_unassigned_pull_requests' to find {pr_count} pull"
" requests that have nobody assigned, then triage each one according to"
" your instructions."
)
response = await call_agent_async(runner, USER_ID, session.id, prompt)