-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat(agent): Implement stale issue bot #3546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
29e1cb8
42b50f9
f665401
61fd8f2
8c672e2
18541fb
3766958
f147867
e399e78
4392624
0bf78bb
9e60195
94b3707
c88441c
b025242
bd4bdb6
289bf0b
0c2bf04
587e61b
0f24138
637bfcf
361d23b
92dd033
c2c2b87
408087f
bcf4509
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # .github/workflows/stale-issue-auditor.yml | ||
|
|
||
| # Best Practice: Always have a 'name' field at the top. | ||
| name: ADK Stale Issue Auditor | ||
|
|
||
| # The 'on' block defines the triggers. | ||
| on: | ||
| # The 'workflow_dispatch' trigger allows manual runs. | ||
| workflow_dispatch: | ||
|
|
||
| # The 'schedule' trigger runs the bot on a timer. | ||
| schedule: | ||
| # This runs at 6:00 AM UTC (e.g., 10 PM PST). | ||
| - cron: '0 6 * * *' | ||
|
|
||
| # The 'jobs' block contains the work to be done. | ||
| jobs: | ||
| # A unique ID for the job. | ||
| audit-stale-issues: | ||
| # The runner environment. | ||
| runs-on: ubuntu-latest | ||
|
|
||
| # Permissions for the job's temporary GITHUB_TOKEN. | ||
| # These are standard and syntactically correct. | ||
| permissions: | ||
| issues: write | ||
| contents: read | ||
|
|
||
| # The sequence of steps for the job. | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: '3.11' | ||
|
|
||
| - name: Install dependencies | ||
| # The '|' character allows for multi-line shell commands. | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install requests google-adk | ||
|
|
||
| - name: Run Auditor Agent Script | ||
| # The 'env' block for setting environment variables. | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.ADK_TRIAGE_AGENT }} | ||
| GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} | ||
| OWNER: google | ||
| REPO: adk-python | ||
| ISSUES_PER_RUN: 100 | ||
| LLM_MODEL_NAME: "gemini-2.5-flash" | ||
| PYTHONPATH: contributing/samples | ||
|
|
||
| # The final 'run' command. | ||
| run: python -m adk_stale_agent.main |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| You are a highly intelligent and transparent repository auditor for '{OWNER}/{REPO}'. | ||
| Your job is to analyze all open issues and report on your findings before taking any action. | ||
|
|
||
| **Primary Directive:** Ignore any events from users ending in `[bot]`. | ||
| **Reporting Directive:** For EVERY issue you analyze, you MUST output a concise, human-readable summary, starting with "Analysis for Issue #[number]:". | ||
|
|
||
| **WORKFLOW:** | ||
| 1. **Context Gathering**: Call `get_repository_maintainers` and `get_all_open_issues`. | ||
| 2. **Per-Issue Analysis**: For each issue, call `get_issue_state`, passing in the maintainers list. | ||
| 3. **Decision & Reporting**: Based on the summary from `get_issue_state`, follow this strict decision tree in order. | ||
|
|
||
| --- **DECISION TREE & REPORTING TEMPLATES** --- | ||
|
|
||
| **STEP 1: CHECK FOR ACTIVITY (IS THE ISSUE ACTIVE?)** | ||
| - **Condition**: Was the last human action NOT from a maintainer? (i.e., `last_human_commenter_is_maintainer` is `False`). | ||
| - **Action**: The author or a third party has acted. The issue is ACTIVE. | ||
| - **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." | ||
| - **If this condition is met, stop processing this issue.** | ||
|
|
||
| **STEP 2: IF PENDING, MANAGE THE STALE LIFECYCLE.** | ||
| - **Condition**: The last human action WAS from a maintainer (`last_human_commenter_is_maintainer` is `True`). The issue is PENDING. | ||
| - **Action**: You must now determine the correct state. | ||
|
|
||
| - **First, check if the issue is already STALE.** | ||
| - **Condition**: Is the `'{STALE_LABEL_NAME}'` label present in `current_labels`? | ||
| - **Action**: The issue is STALE. Your only job is to check if it should be closed. | ||
| - **Get Time Difference**: Call `calculate_time_difference` with the `stale_label_applied_at` timestamp. | ||
| - **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." | ||
|
|
||
| - **ELSE (the issue is PENDING but not yet stale):** | ||
| - **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? | ||
| - **If YES (it is either a question, a request for information, a suggestion, or a request for changes)**: | ||
| - **CRITICAL CHECK**: Now, you must verify the author has not already responded. Compare the `last_author_event_time` and the `last_maintainer_comment_time`. | ||
| - **IF the author has NOT responded** (i.e., `last_author_event_time` is older than `last_maintainer_comment_time` or is null): | ||
| - **Get Time Difference**: Call `calculate_time_difference` with the `last_maintainer_comment_time`. | ||
| - **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." | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This instruction combines multiple actions and a conditional check into a single, complex sentence. Breaking this down into clearer, sequential steps or offloading the conditional logic ( |
||
| - **ELSE (the author HAS responded)**: | ||
| - **Report**: "Analysis for Issue #[number]: PENDING, but author has already responded to the last maintainer request. Action: None." | ||
| - **If NO (it is not a request):** | ||
| - **Report**: "Analysis for Issue #[number]: PENDING. Maintainer's last comment was not a request. Action: None." | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,65 @@ | ||||||
| # ADK Stale Issue Auditor Agent | ||||||
|
|
||||||
| 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. | ||||||
|
|
||||||
| 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. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Core Logic & Features | ||||||
|
|
||||||
| 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). | ||||||
|
|
||||||
| **The agent follows a precise decision tree:** | ||||||
|
|
||||||
| 1. **Audits All Open Issues:** On each run, the agent fetches a batch of the oldest open issues in the repository. | ||||||
| 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. | ||||||
| 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**. | ||||||
| 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: | ||||||
| * Apply a `stale` label to the issue. | ||||||
| * Post a comment notifying the author that the issue is now considered stale and will be closed if no further action is taken. | ||||||
| * Proactively add a `request clarification` label if it's missing, to make the issue's state clear. | ||||||
| 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. | ||||||
| 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. | ||||||
|
|
||||||
| ### Self-Configuration | ||||||
|
|
||||||
| 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. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Configuration | ||||||
|
|
||||||
| The agent is configured entirely via environment variables, which should be set as secrets in the GitHub Actions workflow environment. | ||||||
|
|
||||||
| ### Required Secrets | ||||||
|
|
||||||
| | Secret Name | Description | | ||||||
| | :--- | :--- | | ||||||
| | `GITHUB_TOKEN` | A GitHub Personal Access Token (PAT) with the required permissions. It's recommended to use a PAT from a dedicated "bot" account. | ||||||
| | `GOOGLE_API_KEY` | An API key for the Google AI (Gemini) model used for the agent's reasoning. | ||||||
|
|
||||||
| ### Required PAT Permissions | ||||||
|
|
||||||
| The `GITHUB_TOKEN` requires the following **Repository Permissions**: | ||||||
| * **Issues**: `Read & write` (to read issues, add labels, comment, and close) | ||||||
| * **Administration**: `Read-only` (to read the list of repository collaborators/maintainers) | ||||||
|
|
||||||
| ### Optional Configuration | ||||||
|
|
||||||
| These environment variables can be set in the workflow file to override the defaults in `settings.py`. | ||||||
|
|
||||||
| | Variable Name | Description | Default | | ||||||
| | :--- | :--- | :--- | | ||||||
| | `STALE_HOURS_THRESHOLD` | The number of hours of inactivity after a maintainer's question before an issue is marked as `stale`. | `168` (7 days) | | ||||||
| | `CLOSE_HOURS_AFTER_STALE_THRESHOLD` | The number of hours after being marked `stale` before an issue is closed. | `168` (7 days) | | ||||||
| | `ISSUES_PER_RUN` | The maximum number of oldest open issues to process in a single workflow run. | `100` | | ||||||
| | `LLM_MODEL_NAME`| LLM model to use. | `gemini-2.5-flash` | | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The default model name
Suggested change
|
||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Deployment | ||||||
|
|
||||||
| 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. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
|
|
||||||
| Ensure the necessary repository secrets are configured and the `stale` and `request clarification` labels exist in the repository. | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| # Copyright 2025 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| from . import agent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The instruction
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.is a bit verbose and might be redundant. The LLM should ideally be able to infer the action type from the providedlast_human_action_typefield without explicit instruction to "use the value from the field". Simplifying this could make the prompt more concise.