Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
29e1cb8
feat: Initial implementation of the stale bot agent
ryanaiagent Nov 12, 2025
42b50f9
feat: Implement initial version of the stale bot agent
ryanaiagent Nov 12, 2025
f665401
feat(agent): Implement self-configuring auditor with advanced logic
ryanaiagent Nov 13, 2025
61fd8f2
feat(agent): Renaming and fixing imports
ryanaiagent Nov 13, 2025
8c672e2
feat(agent): Workflow configuration
ryanaiagent Nov 13, 2025
18541fb
feat(agent): Workflow corrections
ryanaiagent Nov 13, 2025
3766958
fix(ci): Correct workflow
ryanaiagent Nov 13, 2025
f147867
fix(ci): Validate and correct workflow YAML syntax
ryanaiagent Nov 13, 2025
e399e78
feat(doc): Add READMEfile
ryanaiagent Nov 13, 2025
4392624
feat(agent): Change get_issue_state logic
ryanaiagent Nov 13, 2025
0bf78bb
fix(agent): Implement pagination and apply autoformatting
ryanaiagent Nov 14, 2025
9e60195
fix(agent): Refine logic, improve performance, and add robustness
ryanaiagent Nov 14, 2025
94b3707
fix(doc): Update README
ryanaiagent Nov 14, 2025
c88441c
fix(agent): refine prompt
ryanaiagent Nov 14, 2025
b025242
fix(ci): fix lint
ryanaiagent Nov 14, 2025
bd4bdb6
refactor(agent): Improve error handling and dynamic configuration and…
ryanaiagent Nov 14, 2025
289bf0b
fix(ci): fix lint errors
ryanaiagent Nov 14, 2025
0c2bf04
fix(chore):Added LLM model as a variable
ryanaiagent Nov 14, 2025
587e61b
fix(chore):Added better exception type handling within calculate_time…
ryanaiagent Nov 14, 2025
0f24138
fix(agent):Refined prompt logic to remove ambiguity
ryanaiagent Nov 14, 2025
637bfcf
feat(agent): Added logic to load prompt intruction file and some doc …
ryanaiagent Nov 14, 2025
361d23b
fix(docs): Update README file
ryanaiagent Nov 14, 2025
92dd033
ci:Update workflow dependencies
ryanaiagent Nov 17, 2025
c2c2b87
refactor: Added copyright headers and complete doc strings to agent t…
ryanaiagent Nov 17, 2025
408087f
refactor(logging): Replace print statements with structured logging
ryanaiagent Nov 17, 2025
bcf4509
fix(logging): Comply with ADK hierarchical logger naming and sort too…
ryanaiagent Nov 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/stale-bot.yml
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
40 changes: 40 additions & 0 deletions contributing/samples/adk_stale_agent/PROMPT_INSTRUCTION.txt
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."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 provided last_human_action_type field without explicit instruction to "use the value from the field". Simplifying this could make the prompt more concise.

- **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."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 (if label name '{REQUEST_CLARIFICATION_LABEL}' is missing) to the tool itself would make the prompt easier for the LLM to parse and execute reliably. For example, the tool add_stale_label_and_comment could internally handle adding the clarification label if it's missing.

- **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."
65 changes: 65 additions & 0 deletions contributing/samples/adk_stale_agent/README.md
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` |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The default model name gemini-2.5-flash appears to be a typo. This is likely meant to be gemini-1.5-flash. Using an incorrect model name will lead to runtime errors.

Suggested change
| `LLM_MODEL_NAME`| LLM model to use. | `gemini-2.5-flash` |
| `LLM_MODEL_NAME`| LLM model to use. | `gemini-1.5-flash` |


---

## 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This line states that a GitHub Actions workflow file (.github/workflows/stale-bot.yml) is included in the PR, but the file is not present in the changes. Please either add the workflow file or update the documentation to clarify that it needs to be created manually.


Ensure the necessary repository secrets are configured and the `stale` and `request clarification` labels exist in the repository.
15 changes: 15 additions & 0 deletions contributing/samples/adk_stale_agent/__init__.py
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
Loading