Skip to content

Commit 23f1d89

Browse files
ryanaiagentxuanyang15
authored andcommitted
docs(agent): Implement stale issue bot
Merge #3546 Co-authored-by: Xuan Yang <xygoogle@google.com> COPYBARA_INTEGRATE_REVIEW=#3546 from ryanaiagent:feat/stale-issue-agent bcf4509 PiperOrigin-RevId: 835327931
1 parent 5583bb8 commit 23f1d89

File tree

8 files changed

+793
-0
lines changed

8 files changed

+793
-0
lines changed

.github/workflows/stale-bot.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# .github/workflows/stale-issue-auditor.yml
2+
3+
# Best Practice: Always have a 'name' field at the top.
4+
name: ADK Stale Issue Auditor
5+
6+
# The 'on' block defines the triggers.
7+
on:
8+
# The 'workflow_dispatch' trigger allows manual runs.
9+
workflow_dispatch:
10+
11+
# The 'schedule' trigger runs the bot on a timer.
12+
schedule:
13+
# This runs at 6:00 AM UTC (e.g., 10 PM PST).
14+
- cron: '0 6 * * *'
15+
16+
# The 'jobs' block contains the work to be done.
17+
jobs:
18+
# A unique ID for the job.
19+
audit-stale-issues:
20+
# The runner environment.
21+
runs-on: ubuntu-latest
22+
23+
# Permissions for the job's temporary GITHUB_TOKEN.
24+
# These are standard and syntactically correct.
25+
permissions:
26+
issues: write
27+
contents: read
28+
29+
# The sequence of steps for the job.
30+
steps:
31+
- name: Checkout repository
32+
uses: actions/checkout@v4
33+
34+
- name: Set up Python
35+
uses: actions/setup-python@v5
36+
with:
37+
python-version: '3.11'
38+
39+
- name: Install dependencies
40+
# The '|' character allows for multi-line shell commands.
41+
run: |
42+
python -m pip install --upgrade pip
43+
pip install requests google-adk
44+
45+
- name: Run Auditor Agent Script
46+
# The 'env' block for setting environment variables.
47+
env:
48+
GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }}
49+
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
50+
OWNER: google
51+
REPO: adk-python
52+
ISSUES_PER_RUN: 100
53+
LLM_MODEL_NAME: "gemini-2.5-flash"
54+
PYTHONPATH: contributing/samples
55+
56+
# The final 'run' command.
57+
run: python -m adk_stale_agent.main
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
You are a highly intelligent and transparent repository auditor for '{OWNER}/{REPO}'.
2+
Your job is to analyze all open issues and report on your findings before taking any action.
3+
4+
**Primary Directive:** Ignore any events from users ending in `[bot]`.
5+
**Reporting Directive:** For EVERY issue you analyze, you MUST output a concise, human-readable summary, starting with "Analysis for Issue #[number]:".
6+
7+
**WORKFLOW:**
8+
1. **Context Gathering**: Call `get_repository_maintainers` and `get_all_open_issues`.
9+
2. **Per-Issue Analysis**: For each issue, call `get_issue_state`, passing in the maintainers list.
10+
3. **Decision & Reporting**: Based on the summary from `get_issue_state`, follow this strict decision tree in order.
11+
12+
--- **DECISION TREE & REPORTING TEMPLATES** ---
13+
14+
**STEP 1: CHECK FOR ACTIVITY (IS THE ISSUE ACTIVE?)**
15+
- **Condition**: Was the last human action NOT from a maintainer? (i.e., `last_human_commenter_is_maintainer` is `False`).
16+
- **Action**: The author or a third party has acted. The issue is ACTIVE.
17+
- **Report and Action**: If '{STALE_LABEL_NAME}' is present, report: "Analysis for Issue #[number]: Issue is ACTIVE. The last action was a [action type] by a non-maintainer. To get the [action type], you MUST use the value from the 'last_human_action_type' field in the summary you received from the tool." Action: Removing stale label and then call `remove_label_from_issue` with the label name '{STALE_LABEL_NAME}'. Otherwise, report: "Analysis for Issue #[number]: Issue is ACTIVE. No stale label to remove. Action: None."
18+
- **If this condition is met, stop processing this issue.**
19+
20+
**STEP 2: IF PENDING, MANAGE THE STALE LIFECYCLE.**
21+
- **Condition**: The last human action WAS from a maintainer (`last_human_commenter_is_maintainer` is `True`). The issue is PENDING.
22+
- **Action**: You must now determine the correct state.
23+
24+
- **First, check if the issue is already STALE.**
25+
- **Condition**: Is the `'{STALE_LABEL_NAME}'` label present in `current_labels`?
26+
- **Action**: The issue is STALE. Your only job is to check if it should be closed.
27+
- **Get Time Difference**: Call `calculate_time_difference` with the `stale_label_applied_at` timestamp.
28+
- **Decision & Report**: If `hours_passed` > **{CLOSE_HOURS_AFTER_STALE_THRESHOLD}**: Report "Analysis for Issue #[number]: STALE. Close threshold met ({CLOSE_HOURS_AFTER_STALE_THRESHOLD} hours) with no author activity." Action: Closing issue and then call `close_as_stale`. Otherwise, report "Analysis for Issue #[number]: STALE. Close threshold not yet met. Action: None."
29+
30+
- **ELSE (the issue is PENDING but not yet stale):**
31+
- **Analyze Intent**: Semantically analyze the `last_maintainer_comment_text`. Is it either a question, a request for information, a suggestion, or a request for changes?
32+
- **If YES (it is either a question, a request for information, a suggestion, or a request for changes)**:
33+
- **CRITICAL CHECK**: Now, you must verify the author has not already responded. Compare the `last_author_event_time` and the `last_maintainer_comment_time`.
34+
- **IF the author has NOT responded** (i.e., `last_author_event_time` is older than `last_maintainer_comment_time` or is null):
35+
- **Get Time Difference**: Call `calculate_time_difference` with the `last_maintainer_comment_time`.
36+
- **Decision & Report**: If `hours_passed` > **{STALE_HOURS_THRESHOLD}**: Report "Analysis for Issue #[number]: PENDING. Stale threshold met ({STALE_HOURS_THRESHOLD} hours)." Action: Marking as stale and then call `add_stale_label_and_comment` and if label name '{REQUEST_CLARIFICATION_LABEL}' is missing then call `add_label_to_issue` with the label name '{REQUEST_CLARIFICATION_LABEL}'. Otherwise, report: "Analysis for Issue #[number]: PENDING. Stale threshold not met. Action: None."
37+
- **ELSE (the author HAS responded)**:
38+
- **Report**: "Analysis for Issue #[number]: PENDING, but author has already responded to the last maintainer request. Action: None."
39+
- **If NO (it is not a request):**
40+
- **Report**: "Analysis for Issue #[number]: PENDING. Maintainer's last comment was not a request. Action: None."
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# ADK Stale Issue Auditor Agent
2+
3+
This directory contains an autonomous agent designed to audit a GitHub repository for stale issues, helping to maintain repository hygiene and ensure that all open items are actionable.
4+
5+
The agent operates as a "Repository Auditor," proactively scanning all open issues rather than waiting for a specific trigger. It uses a combination of deterministic Python tools and the semantic understanding of a Large Language Model (LLM) to make intelligent decisions about the state of a conversation.
6+
7+
---
8+
9+
## Core Logic & Features
10+
11+
The agent's primary goal is to identify issues where a maintainer has requested information from the author, and to manage the lifecycle of that issue based on the author's response (or lack thereof).
12+
13+
**The agent follows a precise decision tree:**
14+
15+
1. **Audits All Open Issues:** On each run, the agent fetches a batch of the oldest open issues in the repository.
16+
2. **Identifies Pending Issues:** It analyzes the full timeline of each issue to see if the last human action was a comment from a repository maintainer.
17+
3. **Semantic Intent Analysis:** If the last comment was from a maintainer, the agent uses the LLM to determine if the comment was a **question or a request for clarification**.
18+
4. **Marks as Stale:** If the maintainer's question has gone unanswered by the author for a configurable period (e.g., 7 days), the agent will:
19+
* Apply a `stale` label to the issue.
20+
* Post a comment notifying the author that the issue is now considered stale and will be closed if no further action is taken.
21+
* Proactively add a `request clarification` label if it's missing, to make the issue's state clear.
22+
5. **Handles Activity:** If any non-maintainer (the author or a third party) comments on an issue, the agent will automatically remove the `stale` label, marking the issue as active again.
23+
6. **Closes Stale Issues:** If an issue remains in the `stale` state for another configurable period (e.g., 7 days) with no new activity, the agent will post a final comment and close the issue.
24+
25+
### Self-Configuration
26+
27+
A key feature of this agent is its ability to self-configure. It does not require a hard-coded list of maintainer usernames. On each run, it uses the GitHub API to dynamically fetch the list of users with write access to the repository, ensuring its logic is always based on the current team.
28+
29+
---
30+
31+
## Configuration
32+
33+
The agent is configured entirely via environment variables, which should be set as secrets in the GitHub Actions workflow environment.
34+
35+
### Required Secrets
36+
37+
| Secret Name | Description |
38+
| :--- | :--- |
39+
| `GITHUB_TOKEN` | A GitHub Personal Access Token (PAT) with the required permissions. It's recommended to use a PAT from a dedicated "bot" account.
40+
| `GOOGLE_API_KEY` | An API key for the Google AI (Gemini) model used for the agent's reasoning.
41+
42+
### Required PAT Permissions
43+
44+
The `GITHUB_TOKEN` requires the following **Repository Permissions**:
45+
* **Issues**: `Read & write` (to read issues, add labels, comment, and close)
46+
* **Administration**: `Read-only` (to read the list of repository collaborators/maintainers)
47+
48+
### Optional Configuration
49+
50+
These environment variables can be set in the workflow file to override the defaults in `settings.py`.
51+
52+
| Variable Name | Description | Default |
53+
| :--- | :--- | :--- |
54+
| `STALE_HOURS_THRESHOLD` | The number of hours of inactivity after a maintainer's question before an issue is marked as `stale`. | `168` (7 days) |
55+
| `CLOSE_HOURS_AFTER_STALE_THRESHOLD` | The number of hours after being marked `stale` before an issue is closed. | `168` (7 days) |
56+
| `ISSUES_PER_RUN` | The maximum number of oldest open issues to process in a single workflow run. | `100` |
57+
| `LLM_MODEL_NAME`| LLM model to use. | `gemini-2.5-flash` |
58+
59+
---
60+
61+
## Deployment
62+
63+
To deploy this agent, a GitHub Actions workflow file (`.github/workflows/stale-bot.yml`) is included. This workflow runs on a daily schedule and executes the agent's main script.
64+
65+
Ensure the necessary repository secrets are configured and the `stale` and `request clarification` labels exist in the repository.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from . import agent

0 commit comments

Comments
 (0)