feat: add cron agents (architecture audit + OpenAPI drift) with manual dispatch#96
feat: add cron agents (architecture audit + OpenAPI drift) with manual dispatch#96
Conversation
… drift) with absolute imports and manual dispatch\n\n- Add .github/workflows/weekly-architecture-audit.yml (schedule + workflow_dispatch)\n- Add .github/workflows/weekly-openapi-drift.yml (schedule + workflow_dispatch)\n- Add scripts/cloud_api.py, scripts/cron_agents.py, and prompts\n- Switch to absolute imports for Python entrypoint\n\nCo-authored-by: openhands <openhands@all-hands.dev>
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds two scheduled GitHub Actions workflows and a new scripts package implementing OpenHands "cron agents": an API client, Jinja2 prompt templates, CLI entrypoint, tasks for architecture-audit and openapi-drift, polling/trajectory handling, report extraction, GitHub-issue posting, and JSON output. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor GitHub as GitHub Actions
participant Runner as Runner (job)
participant Cron as cron_agents.py
participant API as OpenHands API
participant GH as GitHub (issue/PR)
note over GitHub,Runner #DDEBF7: scheduled or manual trigger (Sat 06:00 UTC)
GitHub->>Runner: start job
Runner->>Cron: run task (architecture-audit / openapi-drift)
Cron->>API: test_auth()
API-->>Cron: server_info
Cron->>API: create_conversation(prompt, repo, branch)
API-->>Cron: conversation_id
loop poll until terminal
Cron->>API: get_conversation / get_trajectory
API-->>Cron: status / events
end
Cron->>Cron: extract delimited report (markers)
alt post to issue
Cron->>GH: post comment/update issue (CRON_AGENTS_ISSUE_NUMBER)
GH-->>Cron: comment_id
end
Cron-->>Runner: write JSON result / exit
sequenceDiagram
autonumber
participant Cron as run_openapi_drift_check()
participant FS as Repo filesystem/commands
participant API as OpenHands API
Cron->>FS: try `python scripts/update_openapi.py --check` (fallback to app.openapi())
FS-->>Cron: openapi_diff.json / openapi_diff.md (or generated diff)
Cron->>API: create_conversation(openapi_drift prompt + repo context)
API-->>Cron: conversation_id
Cron->>API: poll_until_complete()
API-->>Cron: status STOPPED
Cron->>API: get_trajectory()
API-->>Cron: assistant messages
Cron->>Cron: parse OPENAPI_DIFF_REPORT_START/END -> extract drift_detected, pr_url, pr_number
Cron-->>FS: emit structured JSON result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested labels
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Summary of Changes
Hello @enyst, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a new set of automated, Python-based cron agents designed to perform critical weekly maintenance tasks within the repository. These agents leverage the OpenHands Cloud API to execute an architecture documentation audit and an OpenAPI specification drift check, ensuring consistency and accuracy over time. The setup supports both scheduled execution for continuous monitoring and manual dispatch for immediate testing and validation.
Highlights
- New Cron Agents Introduced: This pull request adds two new Python-centric cron agents designed for weekly automation tasks within the repository.
- Architecture Documentation Audit: One agent is dedicated to performing a weekly audit of architecture documentation, comparing it against the current codebase to identify and address discrepancies.
- OpenAPI Specification Drift Detection: The second agent focuses on detecting drift in the OpenAPI specification, ensuring the API documentation remains synchronized with the live server implementation and can propose PRs for fixes.
- Robust OpenHands Cloud API Client: A new, clean Python client for the OpenHands Cloud API has been developed, featuring structured error handling, authentication, conversation management, and comprehensive polling capabilities.
- Flexible Execution via GitHub Actions: Both cron agents are integrated into GitHub Actions workflows, supporting scheduled weekly runs and providing a manual dispatch option for immediate testing and validation.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Code Review
This pull request introduces two new cron agents for weekly architecture audits and OpenAPI drift detection. The implementation is well-structured, with a clean API client for OpenHands Cloud, separate scripts for the agent logic, and well-defined prompts. The code is generally robust and includes good error handling and logging.
My review includes a few suggestions to improve maintainability and robustness. I've pointed out an opportunity to reduce significant code duplication in cron_agents.py by refactoring common logic into a helper function. In cloud_api.py, I've suggested using a requests.Session for better performance and replacing a broad exception catch with a more specific one to improve error handling.
…N error handling\n\n- Address AI review feedback: session reuse, specific JSON decode exception\n\nCo-authored-by: openhands <openhands@all-hands.dev>
…ory\n\n- cron_agents: make --api-key optional; prefer OPENHANDS_API_KEY env\n- workflows: remove --api-key arg, set working-directory: scripts\n- cloud_api: requests.Session reuse and narrower JSON exception handling\n\nAddresses AI review suggestions.\n\nCo-authored-by: openhands <openhands@all-hands.dev>
|
I am OpenHands-GPT-5, an AI agent. I reviewed the updated PR description and addressed all actionable AI review feedback:
These changes are pushed to cron-agent and replies were posted on each thread. |
…y: openhands <openhands@all-hands.dev>
…\n- Remove --api-key flag and exclusively read from env\n\nCo-authored-by: openhands <openhands@all-hands.dev>
|
I am OpenHands-GPT-5, an AI agent. Per request, I removed CLI support for the API key entirely. The cron_agents.py entrypoint now reads OPENHANDS_API_KEY only from the environment and fails fast otherwise. Workflows were already updated to avoid passing the key via CLI. |
… keep empty-but-meaningful values\n\n- __post_init__: only create Session if None; set Authorization; setdefault Content-Type/Accept\n- _handle_response: separate status check; require JSON on success with helpful errors\n- create_conversation: include empty list/string values (omit None only)\n\nCo-authored-by: openhands <openhands@all-hands.dev>
…I_KEY)\n\n- Remove --api-key from examples to reflect env-only policy\n\nCo-authored-by: openhands <openhands@all-hands.dev>
…xplicit flows in each task function\n\n- Make cron_agents an example-friendly reference for OH Cloud API usage\n\nCo-authored-by: openhands <openhands@all-hands.dev>
|
I am OpenHands-GPT-5, an AI agent. Update: per goal of making these scripts example-friendly for others to learn from, I reverted the internal helper and expanded both task functions to show the full, explicit flow (client init, auth test, prompt rendering, conversation creation, polling, trajectory fetch, parsing). This improves readability and serves better as reference code, at the cost of some duplication. If you prefer a middle ground, we can keep a small helper just for the polling loop, but I’ve kept everything explicit for now. |
- cloud_api.py -> openhands_api.py - CloudAPIError -> OpenHandsAPIError - OpenHandsCloudClient -> OpenHandsAPIClient - Update argparse help and README wording Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
- scripts/README.md: comprehensive -> structured - scripts/cron_agents.py: comprehensive -> structured Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
…saving JSON artifacts - Create issue #97 as a comment thread for weekly reports - Workflows: pass CRON_AGENTS_ISSUE_NUMBER/GITHUB_TOKEN/GITHUB_REPOSITORY to scripts - Scripts: format readable report and POST to issue comment via GitHub API - Drop JSON artifact upload from cron workflows Co-authored-by: openhands <openhands@all-hands.dev>
… reporting Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
…ngineer. Posting weekly ...' Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
scripts/cron_agents.py (1)
62-74: Normalize event source and coerce values to strings to avoid missed reports and join errors.Case-sensitive 'agent' check can drop messages; non-str content can break join. Align with prior feedback and harden parsing.
- for event in trajectory_data.get('trajectory', []): - # Get message from actions (agent messages) - if event.get('source') == 'agent' and event.get('message'): - messages.append(event.get('message', '')) - - # Get content from observations (tool outputs, etc.) - elif event.get('content'): - messages.append(event.get('content', '')) + for event in trajectory_data.get('trajectory', []): + src = str(event.get('source', '')).lower() + # Get message from actions (agent messages) + if src == 'agent' and event.get('message'): + messages.append(str(event.get('message', ''))) + + # Get content from observations (tool outputs, etc.) + elif event.get('content'): + messages.append(str(event.get('content', '')))
🧹 Nitpick comments (6)
scripts/cron_agents.py (6)
1-10: Fix docstring style to satisfy Ruff D212.Start summaries on the first line; keep a blank line before details.
-""" -OpenHands Cron Agents - Weekly automated tasks using the OpenHands API. - -This module contains the main functions for running weekly automated tasks: -1. Architecture documentation audit -2. OpenAPI drift detection - -Each function handles the complete workflow: prompt rendering, conversation creation, -polling, and result extraction with structured error reporting. -""" +"""OpenHands Cron Agents - Weekly automated tasks using the OpenHands API. + +This module contains the main functions for running weekly automated tasks: +1. Architecture documentation audit +2. OpenAPI drift detection + +Each function handles the complete workflow: prompt rendering, conversation creation, +polling, and result extraction with structured error reporting. +"""- """ - Run weekly architecture documentation audit. - - Returns: + """Run weekly architecture documentation audit. + + Returns: Dict with keys: success, conversation_id, conversation_url, status, report, error - """ + """- """ - Run weekly OpenAPI drift detection. - - Returns: + """Run weekly OpenAPI drift detection. + + Returns: Dict with keys: success, conversation_id, conversation_url, status, report, drift_detected, pr_url, pr_number, error - """ + """Also applies to: 103-108, 207-213
28-33: Avoid configuring global logging at import time.Do it in main() to prevent side effects when imported.
-# Configure logging -logging.basicConfig( - level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' -) logger = logging.getLogger(__name__)def main(): """CLI interface for running cron agents.""" + # Configure logging only for CLI usage + if not logging.getLogger().handlers: + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + )Also applies to: 327-333
121-143: Consider a tiny helper to de-duplicate conversation creation + polling.Keeps tasks explicit while removing repeated scaffolding.
Add helper (outside shown ranges):
def _start_conversation_and_poll( client: OpenHandsAPIClient, *, prompt: str, repository: str, branch: str, instructions: str, poll_timeout: int, ) -> tuple[str, str]: conv = client.create_conversation( initial_user_msg=prompt, repository=repository, selected_branch=branch, conversation_instructions=instructions, ) conversation_id = conv.get('conversation_id') or conv.get('id') if not conversation_id: raise OpenHandsAPIError('No conversation ID returned from API') final_status = client.poll_until_complete( conversation_id, timeout_s=poll_timeout, progress_callback=progress_callback ) return conversation_id, final_statusThen replace per-task blocks with:
- # Create conversation - logger.info('Creating conversation...') - conv_response = client.create_conversation( - initial_user_msg=prompt, - repository=repository, - selected_branch=branch, - conversation_instructions='Focus on architecture documentation accuracy. Only make changes if material discrepancies are found.', - ) - conversation_id = conv_response.get('conversation_id') or conv_response.get('id') - if not conversation_id: - raise OpenHandsAPIError('No conversation ID returned from API') - result['conversation_id'] = conversation_id - result['conversation_url'] = f'{base_url}/conversations/{conversation_id}' - logger.info(f'Conversation created: {result["conversation_url"]}') - # Poll for completion - logger.info('Polling for completion...') - final_status = client.poll_until_complete( - conversation_id, timeout_s=poll_timeout, progress_callback=progress_callback - ) + logger.info('Creating conversation and polling...') + conversation_id, final_status = _start_conversation_and_poll( + client, + prompt=prompt, + repository=repository, + branch=branch, + instructions='Focus on architecture documentation accuracy. Only make changes if material discrepancies are found.', + poll_timeout=poll_timeout, + ) + result['conversation_id'] = conversation_id + result['conversation_url'] = f'{base_url}/conversations/{conversation_id}'Apply similarly in run_openapi_drift_check with its instructions string.
Also applies to: 229-258
299-302: Make 'empty' check case-insensitive and trim whitespace.- pr_url_match = re.search(r'pr_url:\s*(\S+)', report) - if pr_url_match and pr_url_match.group(1) not in ('', 'empty'): - result['pr_url'] = pr_url_match.group(1) + pr_url_match = re.search(r'pr_url:\s*(\S+)', report) + if pr_url_match: + url_val = pr_url_match.group(1).strip() + if url_val and url_val.lower() != 'empty': + result['pr_url'] = url_val
420-424: Use 'Bearer' scheme for GitHub auth header.Both work; 'Bearer' is the current recommendation.
- headers = { - 'Authorization': f'token {github_token}', + headers = { + 'Authorization': f'Bearer {github_token}', 'Accept': 'application/vnd.github+json', 'User-Agent': 'openhands-cron-agents', }
442-446: Create output directory before writing file.Prevents failures when a parent folder is missing.
- if args.output: - with open(args.output, 'w') as f: - json.dump(result, f, indent=2) - logger.info(f'Results saved to {args.output}') + if args.output: + out_path = Path(args.output) + out_path.parent.mkdir(parents=True, exist_ok=True) + with out_path.open('w') as f: + json.dump(result, f, indent=2) + logger.info(f'Results saved to {out_path}')
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
scripts/cron_agents.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/cron_agents.py (1)
scripts/openhands_api.py (6)
OpenHandsAPIClient(35-212)OpenHandsAPIError(20-31)test_auth(92-97)create_conversation(99-124)poll_until_complete(150-212)get_trajectory(143-148)
🪛 Ruff (0.12.2)
scripts/cron_agents.py
1-10: Multi-line docstring summary should start at the first line
Remove whitespace after opening quotes
(D212)
103-108: Multi-line docstring summary should start at the first line
Remove whitespace after opening quotes
(D212)
207-213: Multi-line docstring summary should start at the first line
Remove whitespace after opening quotes
(D212)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Python Tests on Windows (3.12)
…ssages\n\nPrevents missing AGENT/Agent messages in trajectory parsing.\n\nCo-authored-by: openhands <openhands@all-hands.dev>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
scripts/cron_agents.py (7)
1-10: Fix D212: put the summary on the first docstring line.Ruff flags this docstring (and a couple below) with D212. Move the summary up to the opening quotes.
-""" -OpenHands Cron Agents - Weekly automated tasks using the OpenHands API. +"""OpenHands Cron Agents - Weekly automated tasks using the OpenHands API. This module contains the main functions for running weekly automated tasks: 1. Architecture documentation audit 2. OpenAPI drift detection Each function handles the complete workflow: prompt rendering, conversation creation, polling, and result extraction with structured error reporting. """
35-53: Harden template rendering: fail on undefined vars (StrictUndefined).Today missing template variables render to empty strings silently; that can hide errors. Use StrictUndefined.
- template = jinja2.Template(template_content) - return template.render(**kwargs) + env = jinja2.Environment(undefined=jinja2.StrictUndefined) + template = env.from_string(template_content) + return template.render(**kwargs)
104-109: Fix D212 in function docstring.- """ - Run weekly architecture documentation audit. + """Run weekly architecture documentation audit. Returns: Dict with keys: success, conversation_id, conversation_url, status, report, error """
186-198: Consider capping and structuring error payloads.You already truncate to 500 chars. Optionally prefix with a small JSON block (status_code, snippet) for easier downstream parsing.
208-214: Fix D212 in function docstring.- """ - Run weekly OpenAPI drift detection. + """Run weekly OpenAPI drift detection. Returns: Dict with keys: success, conversation_id, conversation_url, status, report, drift_detected, pr_url, pr_number, error """
293-306: Make report parsing resilient to quotes/whitespace.Handle values like pr_url: "https://..." and pr_number: "123".
- pr_url_match = re.search(r'pr_url:\s*(\S+)', report) - if pr_url_match and pr_url_match.group(1) not in ('', 'empty'): - result['pr_url'] = pr_url_match.group(1) + pr_url_match = re.search(r'pr_url:\s*["\']?([^\s"\'\)]+)', report, re.IGNORECASE) + if pr_url_match: + url = pr_url_match.group(1).strip().strip('"\'') + if url and url.lower() not in ('empty', 'none', 'n/a'): + result['pr_url'] = url - - pr_number_match = re.search(r'pr_number:\s*(\d+)', report) - if pr_number_match: - result['pr_number'] = pr_number_match.group(1) + pr_number_match = re.search(r'pr_number:\s*["\']?(\d+)', report, re.IGNORECASE) + if pr_number_match: + result['pr_number'] = pr_number_match.group(1)
421-426: Use Bearer auth and set API version for GitHub.Minor, but aligns with current REST guidelines and reduces warning noise.
- headers = { - 'Authorization': f'token {github_token}', - 'Accept': 'application/vnd.github+json', - 'User-Agent': 'openhands-cron-agents', - } + headers = { + 'Authorization': f'Bearer {github_token}', + 'Accept': 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + 'User-Agent': 'openhands-cron-agents', + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
scripts/cron_agents.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
scripts/cron_agents.py (1)
scripts/openhands_api.py (6)
OpenHandsAPIClient(35-212)OpenHandsAPIError(20-31)test_auth(92-97)create_conversation(99-124)poll_until_complete(150-212)get_trajectory(143-148)
🪛 Ruff (0.12.2)
scripts/cron_agents.py
1-10: Multi-line docstring summary should start at the first line
Remove whitespace after opening quotes
(D212)
104-109: Multi-line docstring summary should start at the first line
Remove whitespace after opening quotes
(D212)
208-214: Multi-line docstring summary should start at the first line
Remove whitespace after opening quotes
(D212)
🔇 Additional comments (3)
scripts/cron_agents.py (3)
170-182: Agent-source normalization looks good.Lowercasing event['source'] prevents missing 'AGENT' messages; string-type guard avoids non-text. Nice.
328-351: Nice: API key only via environment (no CLI secret leakage).Good security posture for CI and local use.
122-124: Resolved: OpenHandsAPIClient session initialization is covered by its @DataClass post_init
I am OpenHands-GPT-5, an AI agent. This PR isolates a minimal, CI-friendly subset of the automation work from #94, focused on two cron agents that use the OpenHands API (works with either cloud-hosted or self-hosted OpenHands).
Included
Explicitly excluded (kept scope small)
Renames and clarity
Why
CI
Notes
Co-authored-by: openhands openhands@all-hands.dev
Summary by CodeRabbit
New Features
Documentation
Chores