From 0a8ec0e3469b34453f8a6ed54181446078e22741 Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 05:54:46 +0000 Subject: [PATCH 01/10] docs: add weekly architecture docs refresh workflow and OpenHands API helper - Add scripts/openhands_api.py minimal client for OpenHands Cloud API - Add scripts/prompts/architecture_refresh.j2 prompt with style/citation/PR requirements - Add .github/workflows/weekly-architecture-refresh.yml to trigger weekly refresh Co-authored-by: openhands --- .../workflows/weekly-architecture-refresh.yml | 39 ++++++ scripts/openhands_api.py | 115 ++++++++++++++++++ scripts/prompts/architecture_refresh.j2 | 44 +++++++ 3 files changed, 198 insertions(+) create mode 100644 .github/workflows/weekly-architecture-refresh.yml create mode 100644 scripts/openhands_api.py create mode 100644 scripts/prompts/architecture_refresh.j2 diff --git a/.github/workflows/weekly-architecture-refresh.yml b/.github/workflows/weekly-architecture-refresh.yml new file mode 100644 index 00000000..af95bf9d --- /dev/null +++ b/.github/workflows/weekly-architecture-refresh.yml @@ -0,0 +1,39 @@ +name: Weekly architecture docs refresh + +on: + schedule: + - cron: '0 7 * * 1' # Mondays 07:00 UTC + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + refresh-architecture: + runs-on: ubuntu-latest + if: github.actor != 'github-actions[bot]' + env: + OPENHANDS_API_KEY: ${{ secrets.OPENHANDS_API_KEY }} + steps: + - name: Checkout docs repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Run OpenHands conversation to refresh architecture docs + run: | + python scripts/openhands_api.py \ + --prompt scripts/prompts/architecture_refresh.j2 \ + --repo OpenHands/docs \ + --base-url https://app.all-hands.dev + + - name: Log follow-up + run: | + echo "If the agent proposed changes, it will open a PR titled 'docs: weekly architecture docs refresh'." + echo "Otherwise, it will comment that no updates are needed." diff --git a/scripts/openhands_api.py b/scripts/openhands_api.py new file mode 100644 index 00000000..fec7120c --- /dev/null +++ b/scripts/openhands_api.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +""" +Minimal helper to invoke the OpenHands Cloud API to start a conversation +for weekly documentation automation tasks. + +Environment: +- OPENHANDS_API_KEY: required. Cloud API key created at https://app.all-hands.dev/settings/api-keys + +Usage: + python scripts/openhands_api.py \ + --prompt scripts/prompts/architecture_refresh.j2 \ + --repo OpenHands/docs \ + [--base-url https://app.all-hands.dev] + +Notes: +- This helper only creates the conversation and prints its URL. The remote + agent (per your prompt) is expected to open any PRs. +""" +from __future__ import annotations + +import argparse +import os +import sys +import textwrap +from dataclasses import dataclass +from typing import Any, Dict + +import json +import time +import urllib.request + + +DEFAULT_BASE_URL = "https://app.all-hands.dev" +API_TIMEOUT = 30 + + +@dataclass +class Conversation: + id: str + status: str | None + + +def _http_json(url: str, method: str, headers: Dict[str, str], data: Dict[str, Any] | None) -> Dict[str, Any]: + req = urllib.request.Request(url=url, method=method) + for k, v in headers.items(): + req.add_header(k, v) + if data is not None: + payload = json.dumps(data).encode("utf-8") + req.data = payload + with urllib.request.urlopen(req, timeout=API_TIMEOUT) as resp: + body = resp.read().decode("utf-8") + if not body: + return {} + return json.loads(body) + + +def create_conversation(base_url: str, api_key: str, initial_user_msg: str, repo: str | None = None) -> Conversation: + url = base_url.rstrip("/") + "/api/conversations" + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + "Accept": "application/json", + } + payload: Dict[str, Any] = {"initial_user_msg": initial_user_msg} + if repo: + payload["repository"] = repo + data = _http_json(url, "POST", headers, payload) + conv_id = data.get("conversation_id") or data.get("id") or "" + status = data.get("status") + if not conv_id: + raise RuntimeError(f"Unexpected response creating conversation: {data}") + return Conversation(id=str(conv_id), status=status) + + +def read_text(path: str) -> str: + with open(path, "r", encoding="utf-8") as f: + return f.read() + + +def build_prompt(prompt_file: str) -> str: + return read_text(prompt_file) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--prompt", required=True, help="Path to prompt file (j2 or txt)") + parser.add_argument("--repo", required=False, help="Target GitHub repo for the agent to work on, e.g. OpenHands/docs") + parser.add_argument("--base-url", default=DEFAULT_BASE_URL, help="OpenHands API base URL") + args = parser.parse_args() + + api_key = os.getenv("OPENHANDS_API_KEY") or os.getenv("OPENHANDS_CLOUD_API_KEY") or os.getenv("ALL_HANDS_BOT") + if not api_key: + print("ERROR: OPENHANDS_API_KEY (or OPENHANDS_CLOUD_API_KEY / ALL_HANDS_BOT) is not set.", file=sys.stderr) + return 2 + + try: + initial_user_msg = build_prompt(args.prompt) + except Exception as e: + print(f"ERROR: failed to read prompt: {e}", file=sys.stderr) + return 2 + + try: + conv = create_conversation(args.base_url, api_key, initial_user_msg, repo=args.repo) + except Exception as e: + print(f"ERROR: failed to create conversation: {e}", file=sys.stderr) + return 1 + + conv_url = args.base_url.rstrip("/") + f"/conversations/{conv.id}" + print(f"Created conversation: {conv.id}") + print(f"URL: {conv_url}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/prompts/architecture_refresh.j2 b/scripts/prompts/architecture_refresh.j2 new file mode 100644 index 00000000..6135fc51 --- /dev/null +++ b/scripts/prompts/architecture_refresh.j2 @@ -0,0 +1,44 @@ +You are OpenHands, operating via the OpenHands Cloud API. Your task is to refresh the weekly SDK architecture documentation in the OpenHands/docs repository under sdk/arch. + +Scope and goals: +- Review the current architecture docs in this repo under sdk/arch for OpenHands V1 (software-agent-sdk and agent server). +- Compare against the latest code in: + - https://github.com/OpenHands/software-agent-sdk + - https://github.com/OpenHands/OpenHands (for the agent server portion) +- If substantive differences or missing sections are found, prepare updated pages and open a PR to OpenHands/docs. + +Style and factuality requirements: + +Do not invent behavior; verify every statement against repository code or existing docs. +Cite sources inline with file paths and short context, e.g., (source: openhands/agent_server/conversation.py). +Keep tone clear, concise, and neutral. +Prefer Mermaid diagrams for flows, classes, and sequences. +Include a Last updated timestamp and Source commit (git rev-parse HEAD) at the bottom of each page. + +Pull Request requirements: + +If you propose changes compared to the previous week, open a PR yourself. +Read and follow .github/pull_request_template.md. +Title: "docs: weekly architecture docs refresh". +Body: summarize updated/added pages and major changes; list sources cited. +Run pre-commit hooks and ensure CI passes. + +Documentation framework requirements: +- Follow the Guide for Writing Architecture Component Documentation included in this repo (docs/.openhands/microagents/sdk-guidelines.md) for structure and length. +- Prioritize these pages if updates are needed: overview.mdx, agent.mdx, conversation.mdx, llm.mdx, tool-system.mdx, workspace.mdx, agent-server.mdx, events.mdx, condenser.mdx, security.mdx. +- Ensure diagrams are simple and consistent (Mermaid, max ~12 nodes, consistent styles as in the guide). +- Use inline GitHub links to source files for every component mentioned in tables. + +Special requirement for Event Stream docs: +- Include a dedicated section named "Event Stream Subscription Mechanism" documenting how to subscribe to the event stream and one real-world example use case (intercept during headless run, inject instructions, monitor progress). Verify against source (e.g., see subscription/emit points in the agent server implementation). + +Operational constraints: +- Work only within architecture docs (sdk/arch). Do not modify guides or API reference unless strictly necessary for correctness. +- Keep changes focused and minimal. +- If no changes are necessary, post a short comment in the conversation stating no updates needed this week with a summary of checks performed. + +Output format for proposed changes: +- For each updated page: a succinct summary of what changed, and the list of sources with inline citations. +- Use GitHub-flavored Markdown compatible with our Mintlify setup. + +Begin by auditing the current sdk/arch pages versus HEAD of the two repositories. Then either open a PR with updates or state that no changes are required. From 61fa9bd02a3bc21c2ad4e4621494e91bea0db551 Mon Sep 17 00:00:00 2001 From: Engel Nyst Date: Tue, 9 Dec 2025 07:24:30 +0100 Subject: [PATCH 02/10] Update scripts/openhands_api.py --- scripts/openhands_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/openhands_api.py b/scripts/openhands_api.py index fec7120c..a20bce32 100644 --- a/scripts/openhands_api.py +++ b/scripts/openhands_api.py @@ -14,7 +14,7 @@ Notes: - This helper only creates the conversation and prints its URL. The remote - agent (per your prompt) is expected to open any PRs. + agent is expected to open any PRs. """ from __future__ import annotations From b2b07746336c6932006cb06571c0745435ddcdfc Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 06:27:42 +0000 Subject: [PATCH 03/10] refactor: use builtin dict[] annotations (drop typing.Dict) for Python 3.12 Co-authored-by: openhands --- scripts/openhands_api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/openhands_api.py b/scripts/openhands_api.py index a20bce32..19f2a37d 100644 --- a/scripts/openhands_api.py +++ b/scripts/openhands_api.py @@ -23,7 +23,7 @@ import sys import textwrap from dataclasses import dataclass -from typing import Any, Dict +from typing import Any import json import time @@ -40,7 +40,7 @@ class Conversation: status: str | None -def _http_json(url: str, method: str, headers: Dict[str, str], data: Dict[str, Any] | None) -> Dict[str, Any]: +def _http_json(url: str, method: str, headers: dict[str, str], data: dict[str, Any] | None) -> dict[str, Any]: req = urllib.request.Request(url=url, method=method) for k, v in headers.items(): req.add_header(k, v) @@ -61,7 +61,7 @@ def create_conversation(base_url: str, api_key: str, initial_user_msg: str, repo "Content-Type": "application/json", "Accept": "application/json", } - payload: Dict[str, Any] = {"initial_user_msg": initial_user_msg} + payload: dict[str, Any] = {"initial_user_msg": initial_user_msg} if repo: payload["repository"] = repo data = _http_json(url, "POST", headers, payload) From dd76453b3491d514d994883ca24f86da8d123927 Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 06:30:34 +0000 Subject: [PATCH 04/10] refactor: switch OpenHands API helper to requests.Session and JSON helpers - Replace urllib with requests - Keep simple _http_json wrapper for consistency, but leverage response.json() Co-authored-by: openhands --- scripts/openhands_api.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/openhands_api.py b/scripts/openhands_api.py index 19f2a37d..4c02c8b1 100644 --- a/scripts/openhands_api.py +++ b/scripts/openhands_api.py @@ -27,7 +27,7 @@ import json import time -import urllib.request +import requests DEFAULT_BASE_URL = "https://app.all-hands.dev" @@ -41,17 +41,16 @@ class Conversation: def _http_json(url: str, method: str, headers: dict[str, str], data: dict[str, Any] | None) -> dict[str, Any]: - req = urllib.request.Request(url=url, method=method) - for k, v in headers.items(): - req.add_header(k, v) - if data is not None: - payload = json.dumps(data).encode("utf-8") - req.data = payload - with urllib.request.urlopen(req, timeout=API_TIMEOUT) as resp: - body = resp.read().decode("utf-8") - if not body: - return {} - return json.loads(body) + with requests.Session() as s: + s.headers.update(headers) + if method.upper() == "POST": + r = s.post(url, json=data, timeout=API_TIMEOUT) + elif method.upper() == "GET": + r = s.get(url, timeout=API_TIMEOUT) + else: + r = s.request(method.upper(), url, json=data, timeout=API_TIMEOUT) + r.raise_for_status() + return r.json() if r.content else {} def create_conversation(base_url: str, api_key: str, initial_user_msg: str, repo: str | None = None) -> Conversation: From ac56fd867b6304eda465c7ae48dbe339df5cc8b6 Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 06:33:58 +0000 Subject: [PATCH 05/10] chore: use uv in workflow and inline requests calls (drop helper) - Use astral-sh/setup-uv and uv run with Python 3.12 - Inline requests Session calls; remove urllib/json glue Co-authored-by: openhands --- .../workflows/weekly-architecture-refresh.yml | 8 +++---- scripts/openhands_api.py | 24 +++++-------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/.github/workflows/weekly-architecture-refresh.yml b/.github/workflows/weekly-architecture-refresh.yml index af95bf9d..06981beb 100644 --- a/.github/workflows/weekly-architecture-refresh.yml +++ b/.github/workflows/weekly-architecture-refresh.yml @@ -21,14 +21,12 @@ jobs: with: fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' + - name: Install uv + uses: astral-sh/setup-uv@v7 - name: Run OpenHands conversation to refresh architecture docs run: | - python scripts/openhands_api.py \ + uv run --python 3.12 python scripts/openhands_api.py \ --prompt scripts/prompts/architecture_refresh.j2 \ --repo OpenHands/docs \ --base-url https://app.all-hands.dev diff --git a/scripts/openhands_api.py b/scripts/openhands_api.py index 4c02c8b1..9ea36ffa 100644 --- a/scripts/openhands_api.py +++ b/scripts/openhands_api.py @@ -21,17 +21,14 @@ import argparse import os import sys -import textwrap from dataclasses import dataclass from typing import Any -import json -import time import requests DEFAULT_BASE_URL = "https://app.all-hands.dev" -API_TIMEOUT = 30 +API_TIMEOUT = 30 # seconds @dataclass @@ -40,19 +37,6 @@ class Conversation: status: str | None -def _http_json(url: str, method: str, headers: dict[str, str], data: dict[str, Any] | None) -> dict[str, Any]: - with requests.Session() as s: - s.headers.update(headers) - if method.upper() == "POST": - r = s.post(url, json=data, timeout=API_TIMEOUT) - elif method.upper() == "GET": - r = s.get(url, timeout=API_TIMEOUT) - else: - r = s.request(method.upper(), url, json=data, timeout=API_TIMEOUT) - r.raise_for_status() - return r.json() if r.content else {} - - def create_conversation(base_url: str, api_key: str, initial_user_msg: str, repo: str | None = None) -> Conversation: url = base_url.rstrip("/") + "/api/conversations" headers = { @@ -63,7 +47,11 @@ def create_conversation(base_url: str, api_key: str, initial_user_msg: str, repo payload: dict[str, Any] = {"initial_user_msg": initial_user_msg} if repo: payload["repository"] = repo - data = _http_json(url, "POST", headers, payload) + with requests.Session() as s: + s.headers.update(headers) + r = s.post(url, json=payload, timeout=API_TIMEOUT) + r.raise_for_status() + data = r.json() conv_id = data.get("conversation_id") or data.get("id") or "" status = data.get("status") if not conv_id: From 522984e8d1544e6e3684fd6f52dd5aa7556f4fe3 Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 06:35:11 +0000 Subject: [PATCH 06/10] fix(prompt): correct agent-server source location (it lives in software-agent-sdk under openhands-agent-server/) Co-authored-by: openhands --- scripts/prompts/architecture_refresh.j2 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/prompts/architecture_refresh.j2 b/scripts/prompts/architecture_refresh.j2 index 6135fc51..7d4e1275 100644 --- a/scripts/prompts/architecture_refresh.j2 +++ b/scripts/prompts/architecture_refresh.j2 @@ -3,8 +3,7 @@ You are OpenHands, operating via the OpenHands Cloud API. Your task is to refres Scope and goals: - Review the current architecture docs in this repo under sdk/arch for OpenHands V1 (software-agent-sdk and agent server). - Compare against the latest code in: - - https://github.com/OpenHands/software-agent-sdk - - https://github.com/OpenHands/OpenHands (for the agent server portion) + - https://github.com/OpenHands/software-agent-sdk (this repo contains the SDK and the agent-server package: openhands-agent-server/) - If substantive differences or missing sections are found, prepare updated pages and open a PR to OpenHands/docs. Style and factuality requirements: From 3408a16c433b9ac76659ac505f49c3d37f867039 Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 06:37:00 +0000 Subject: [PATCH 07/10] chore: only accept OPENHANDS_API_KEY in helper (drop alternate envs)\n\nCo-authored-by: openhands --- scripts/openhands_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/openhands_api.py b/scripts/openhands_api.py index 9ea36ffa..860a77a5 100644 --- a/scripts/openhands_api.py +++ b/scripts/openhands_api.py @@ -75,9 +75,9 @@ def main() -> int: parser.add_argument("--base-url", default=DEFAULT_BASE_URL, help="OpenHands API base URL") args = parser.parse_args() - api_key = os.getenv("OPENHANDS_API_KEY") or os.getenv("OPENHANDS_CLOUD_API_KEY") or os.getenv("ALL_HANDS_BOT") + api_key = os.getenv("OPENHANDS_API_KEY") if not api_key: - print("ERROR: OPENHANDS_API_KEY (or OPENHANDS_CLOUD_API_KEY / ALL_HANDS_BOT) is not set.", file=sys.stderr) + print("ERROR: OPENHANDS_API_KEY is not set.", file=sys.stderr) return 2 try: From 28f63cf2ce2d40173fb7f54b136e507c49147ac1 Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 06:43:43 +0000 Subject: [PATCH 08/10] chore(prompt): drop V0 event-stream special section; reference only software-agent-sdk HEAD for audit\n\nCo-authored-by: openhands --- scripts/prompts/architecture_refresh.j2 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/prompts/architecture_refresh.j2 b/scripts/prompts/architecture_refresh.j2 index 7d4e1275..de133f09 100644 --- a/scripts/prompts/architecture_refresh.j2 +++ b/scripts/prompts/architecture_refresh.j2 @@ -28,8 +28,6 @@ Documentation framework requirements: - Ensure diagrams are simple and consistent (Mermaid, max ~12 nodes, consistent styles as in the guide). - Use inline GitHub links to source files for every component mentioned in tables. -Special requirement for Event Stream docs: -- Include a dedicated section named "Event Stream Subscription Mechanism" documenting how to subscribe to the event stream and one real-world example use case (intercept during headless run, inject instructions, monitor progress). Verify against source (e.g., see subscription/emit points in the agent server implementation). Operational constraints: - Work only within architecture docs (sdk/arch). Do not modify guides or API reference unless strictly necessary for correctness. @@ -40,4 +38,4 @@ Output format for proposed changes: - For each updated page: a succinct summary of what changed, and the list of sources with inline citations. - Use GitHub-flavored Markdown compatible with our Mintlify setup. -Begin by auditing the current sdk/arch pages versus HEAD of the two repositories. Then either open a PR with updates or state that no changes are required. +Begin by auditing the current sdk/arch pages versus HEAD of the software-agent-sdk repository. Then either open a PR with updates or state that no changes are required. From 57713d9b3d623c84b0232286303309812d5c2965 Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 06:44:35 +0000 Subject: [PATCH 09/10] ci: ensure requests installed via uv for weekly arch refresh workflow\n\nCo-authored-by: openhands --- .github/workflows/weekly-architecture-refresh.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/weekly-architecture-refresh.yml b/.github/workflows/weekly-architecture-refresh.yml index 06981beb..9e77badd 100644 --- a/.github/workflows/weekly-architecture-refresh.yml +++ b/.github/workflows/weekly-architecture-refresh.yml @@ -24,6 +24,10 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v7 + - name: Install dependencies + run: | + uv pip install requests + - name: Run OpenHands conversation to refresh architecture docs run: | uv run --python 3.12 python scripts/openhands_api.py \ From ebc78fdbc363c4b7b9a7f8c0b857e2d9503a4a16 Mon Sep 17 00:00:00 2001 From: enyst Date: Tue, 9 Dec 2025 06:47:21 +0000 Subject: [PATCH 10/10] docs(prompt): require Markdown links to GitHub source for citations (with correct agent-server path)\n\nCo-authored-by: openhands --- scripts/prompts/architecture_refresh.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/prompts/architecture_refresh.j2 b/scripts/prompts/architecture_refresh.j2 index de133f09..3983a625 100644 --- a/scripts/prompts/architecture_refresh.j2 +++ b/scripts/prompts/architecture_refresh.j2 @@ -9,7 +9,7 @@ Scope and goals: Style and factuality requirements: Do not invent behavior; verify every statement against repository code or existing docs. -Cite sources inline with file paths and short context, e.g., (source: openhands/agent_server/conversation.py). +Cite sources inline using Markdown links to GitHub source files with short context, e.g., [api.py](https://github.com/OpenHands/software-agent-sdk/blob/HEAD/openhands-agent-server/openhands/agent_server/api.py). Keep tone clear, concise, and neutral. Prefer Mermaid diagrams for flows, classes, and sequences. Include a Last updated timestamp and Source commit (git rev-parse HEAD) at the bottom of each page.