Skip to content

Add REST API contract summaries to PR descriptions#3789

Open
enyst wants to merge 4 commits into
mainfrom
rest-api-pr-summary
Open

Add REST API contract summaries to PR descriptions#3789
enyst wants to merge 4 commits into
mainfrom
rest-api-pr-summary

Conversation

@enyst

@enyst enyst commented Jun 18, 2026

Copy link
Copy Markdown
Member

HUMAN:
We all know API design matters, for usability, maintainability and all; the funny thing we need to grapple with, is that it’s possible that API design is one of the (few?) things where humans need to think things over.

This PR proposes adding a concise view of API changes to the PR descriptions. Even when the incompatible REST API checks workflow succeeds, it means additions, and it’s good to know to add the right things to avoid changing them too soon.


AGENT:

This PR was created by an AI agent (OpenHands) on behalf of the user.

Why

Recent agent-server REST API PRs (#3770, #3784, and #3788) made API-review details hard to see at a glance from the normal PR description. The existing REST API workflow already generates and compares OpenAPI contracts; this adds a concise PR-description summary for additions, removals, and modifications against the PR base.

Summary

  • Add a contract-summary generator that flattens the public /api/** OpenAPI surface into operation/schema lines and emits a concise unified diff against the PR base SHA.
  • Add a PR-body updater that inserts, replaces, or removes a marked REST API contract block inside the existing ## Summary section without touching the HUMAN section.
  • Extend the REST API breakage workflow to generate the diff for PRs and update same-repository PR descriptions when the public REST contract changes.

REST API contract changes

Compared with base OpenAPI 9a3721a25dd8 for public /api/** paths.

--- base public OpenAPI
+++ head public OpenAPI
@@ -69,0 +70,3 @@
+operation POST /api/conversations/{conversation_id}/goal operationId=start_goal_in_conversation_api_conversations__conversation_id__goal_post
+operation POST /api/conversations/{conversation_id}/goal/resume operationId=resume_goal_in_conversation_api_conversations__conversation_id__goal_resume_post
+operation POST /api/conversations/{conversation_id}/goal/stop operationId=stop_goal_in_conversation_api_conversations__conversation_id__goal_stop_post
@@ -173,0 +177,3 @@
+parameter POST /api/conversations/{conversation_id}/goal path:conversation_id required=true schema=type="string" format="uuid"
+parameter POST /api/conversations/{conversation_id}/goal/resume path:conversation_id required=true schema=type="string" format="uuid"
+parameter POST /api/conversations/{conversation_id}/goal/stop path:conversation_id required=true schema=type="string" format="uuid"
@@ -201,0 +208 @@
+requestBody POST /api/conversations/{conversation_id}/goal application/json required=true schema=StartGoalRequest
@@ -356,0 +364,11 @@
+response POST /api/conversations/{conversation_id}/goal 200 application/json schema=Success
+response POST /api/conversations/{conversation_id}/goal 404 no-content
+response POST /api/conversations/{conversation_id}/goal 409 no-content
+response POST /api/conversations/{conversation_id}/goal 422 application/json schema=HTTPValidationError
+response POST /api/conversations/{conversation_id}/goal/resume 200 application/json schema=Success
+response POST /api/conversations/{conversation_id}/goal/resume 404 no-content
+response POST /api/conversations/{conversation_id}/goal/resume 409 no-content
+response POST /api/conversations/{conversation_id}/goal/resume 422 application/json schema=HTTPValidationError
+response POST /api/conversations/{conversation_id}/goal/stop 200 application/json schema=Success
+response POST /api/conversations/{conversation_id}/goal/stop 404 no-content
+response POST /api/conversations/{conversation_id}/goal/stop 422 application/json schema=HTTPValidationError
@@ -421 +438,0 @@
-schema ACPAgent-Input property acp_env optional schema=type="object" additionalProperties=type="string"
@@ -446 +462,0 @@
-schema ACPAgent-Output property acp_env optional schema=type="object" additionalProperties=type="string"
@@ -979 +995 @@
-schema ConversationInfo property launched_profile optional schema=anyOf=[LaunchedProfile,type="null"]
+schema ConversationInfo property launched_agent_profile optional schema=anyOf=[LaunchedAgentProfile,type="null"]
@@ -1471,3 +1487,3 @@
-schema LaunchedProfile property profile_id required schema=type="string" format="uuid"
-schema LaunchedProfile property revision required schema=type="integer" minimum=0.0
-schema LaunchedProfile type="object"
+schema LaunchedAgentProfile property agent_profile_id required schema=type="string" format="uuid"
+schema LaunchedAgentProfile property revision required schema=type="integer" minimum=0.0
+schema LaunchedAgentProfile type="object"
@@ -1890,0 +1907,3 @@
+schema StartGoalRequest property max_iterations optional schema=type="integer" default=10 minimum=1.0
+schema StartGoalRequest property objective required schema=type="string"
+schema StartGoalRequest type="object"

Issue Number

N/A

How to Test

  • make build
  • uv run pytest tests/cross/test_agent_server_rest_api_contract_summary.py
  • uv run pytest tests/cross/test_check_agent_server_rest_api_breakage.py tests/cross/test_agent_server_rest_api_contract_summary.py
  • uv run --with packaging python .github/scripts/generate_agent_server_rest_api_contract_summary.py --base-ref origin/main --output /tmp/rest-api-contract-summary.md
  • uv run pre-commit run --files .github/scripts/generate_agent_server_rest_api_contract_summary.py .github/scripts/update_pr_body_with_rest_api_summary.py .github/workflows/agent-server-rest-api-breakage.yml tests/cross/test_agent_server_rest_api_contract_summary.py

Video/Screenshots

N/A

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

  • The PR-description update is skipped for fork PRs because the token is read-only there.
  • When no REST API contract changes remain, the workflow removes its previously generated marked block to avoid stale summaries.

@enyst can click here to continue refining the PR


Agent Server images for this PR

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python amd64, arm64 nikolaik/python-nodejs:python3.13-nodejs22-slim Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:bd4d917-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-bd4d917-python \
  ghcr.io/openhands/agent-server:bd4d917-python

All tags pushed for this build

ghcr.io/openhands/agent-server:bd4d917-golang-amd64
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-golang-amd64
ghcr.io/openhands/agent-server:rest-api-pr-summary-golang-amd64
ghcr.io/openhands/agent-server:bd4d917-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:bd4d917-golang-arm64
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-golang-arm64
ghcr.io/openhands/agent-server:rest-api-pr-summary-golang-arm64
ghcr.io/openhands/agent-server:bd4d917-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:bd4d917-java-amd64
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-java-amd64
ghcr.io/openhands/agent-server:rest-api-pr-summary-java-amd64
ghcr.io/openhands/agent-server:bd4d917-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:bd4d917-java-arm64
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-java-arm64
ghcr.io/openhands/agent-server:rest-api-pr-summary-java-arm64
ghcr.io/openhands/agent-server:bd4d917-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:bd4d917-python-amd64
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-python-amd64
ghcr.io/openhands/agent-server:rest-api-pr-summary-python-amd64
ghcr.io/openhands/agent-server:bd4d917-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-amd64
ghcr.io/openhands/agent-server:bd4d917-python-arm64
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-python-arm64
ghcr.io/openhands/agent-server:rest-api-pr-summary-python-arm64
ghcr.io/openhands/agent-server:bd4d917-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim-arm64
ghcr.io/openhands/agent-server:bd4d917-golang
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-golang
ghcr.io/openhands/agent-server:rest-api-pr-summary-golang
ghcr.io/openhands/agent-server:bd4d917-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:bd4d917-java
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-java
ghcr.io/openhands/agent-server:rest-api-pr-summary-java
ghcr.io/openhands/agent-server:bd4d917-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:bd4d917-python
ghcr.io/openhands/agent-server:bd4d9175660fd2916057a7c20049eda7e30d286c-python
ghcr.io/openhands/agent-server:rest-api-pr-summary-python
ghcr.io/openhands/agent-server:bd4d917-nikolaik_s_python-nodejs_tag_python3.13-nodejs22-slim

About Multi-Architecture Support

  • Each variant tag (e.g., bd4d917-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., bd4d917-python-amd64) are also available if needed

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Python API breakage checks — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@enyst enyst marked this pull request as ready for review June 18, 2026 13:48
@enyst enyst added the review-this This label triggers a PR review by OpenHands label Jun 18, 2026
@enyst enyst mentioned this pull request Jun 18, 2026
5 tasks

all-hands-bot commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

Review complete.

This review was performed through OpenHands Cloud Automation. You can log in and view the conversation here.

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟡 Taste Rating: Acceptable - The summary generation is simple and the focused tests pass, but the PR-body updater has a user-visible edge case around the protected HUMAN section.

[CRITICAL ISSUES]

  • [.github/scripts/update_pr_body_with_rest_api_summary.py, Line 22] Data Ownership / Compatibility: _summary_bounds() searches the entire PR body for the first ## Summary. If the human-authored section contains its own ## Summary, the workflow inserts the generated block into the wrong part of the description instead of the AGENT summary, violating the template's "Do not edit the HUMAN section" contract. Scope the search to the AGENT section (or an explicit marker after the `---

AGENT:boundary) before locating## Summary`.

[TESTING GAPS]

  • Add a regression test where the HUMAN section contains a ## Summary heading and the AGENT section also contains ## Summary; the generated block should land in the AGENT summary and the HUMAN text should remain unchanged.

[RISK ASSESSMENT]

  • [Overall PR] ⚠️ Risk Assessment: 🟡 MEDIUM
    This is workflow/tooling-only and does not alter SDK runtime behavior, but it grants the workflow pull-requests: write and automatically patches PR descriptions. The current section-selection bug can mutate or insert into protected author-controlled text on same-repository PRs, so it should be fixed before merge.

VERDICT:
Needs rework: Fix the PR-body section targeting so the automation cannot touch the HUMAN section.

KEY INSIGHT:
The core data-ownership boundary is the HUMAN/AGENT split, but the updater currently treats the whole PR body as one undifferentiated string.


Improve this review? If any feedback above seems incorrect or irrelevant to this repository, you can teach the reviewer to do better:

  1. Add a .agents/skills/custom-codereview-guide.md file to your branch (or edit it if one already exists) with the /codereview trigger and the context the reviewer is missing (e.g., "Security concerns about X do not apply here because Y"). See the customization docs for the required frontmatter format.
  2. Re-request a review - the reviewer reads guidelines from the PR branch, so your changes take effect immediately.
  3. When your PR is merged, the guideline file goes through normal code review by repository maintainers.

Resolve with AI? Install the iterate skill in your agent and run /iterate to automatically drive this PR through CI, review, and QA until it's merge-ready.

Was this review helpful? React with 👍 or 👎 to give feedback.

This review was generated by an AI agent (OpenHands) on behalf of the user through OpenHands Automation. View conversation

Comment thread .github/scripts/update_pr_body_with_rest_api_summary.py Outdated

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

⚠️ QA Report: PASS WITH ISSUES

The REST API contract summary flow works end-to-end in local workflow-style execution, with one minor side effect around PR-body newline normalization.

Does this PR achieve its stated goal?

Yes. The new generator runs on the PR branch, emits no summary for this PR because the public /api/** contract is unchanged, and emits a concise diff when I temporarily added a real public REST endpoint. The PR-body updater inserted that generated block inside ## Summary and removed it again when the summary became empty, while preserving the visible HUMAN text.

Phase Result
Environment Setup make build completed successfully
CI Status gh pr checks showed 36 passing checks and 15 skipped checks
Functional Verification ⚠️ Generator and updater behavior verified; minor unnecessary body rewrite found
Functional Verification

Test 1: REST API contract summary generator

Step 1 — Establish baseline without the PR feature:
Ran the new CLI path from a detached worktree at the PR base SHA:

python: can't open file '/tmp/software-agent-sdk-base-3789/.github/scripts/generate_agent_server_rest_api_contract_summary.py': [Errno 2] No such file or directory
base_cli_exit=2

This shows the PR is adding a new workflow-facing CLI entrypoint that did not exist on main.

Step 2 — Apply the PR's changes:
Used the checked-out PR branch at 01822c13634427543ba881dbc1306f1607cc7389 after make build.

Step 3 — Run with the PR in place against the actual PR base:
Ran:

uv run --with packaging python .github/scripts/generate_agent_server_rest_api_contract_summary.py --base-ref 9a3721a25dd8b8cbc742350fe3b30c2d3d130807 --output /tmp/rest-api-contract-summary-pr3789.md
summary_bytes=0
summary_preview=<>

This shows the generator completed successfully and correctly produced an empty summary for this PR, which does not itself change the public REST API contract.

Step 4 — Exercise a real public REST API addition:
Temporarily added a QA-only GET /api/tools/qa-contract-summary endpoint to the local working tree, then reran the same generator command:

summary_bytes=436
### REST API contract changes

Compared with base OpenAPI `9a3721a25dd8` for public `/api/**` paths.

```diff
--- base public OpenAPI
+++ head public OpenAPI
@@ -49,0 +50 @@
+operation GET /api/tools/qa-contract-summary operationId=qa_contract_summary_probe_api_tools_qa_contract_summary_get
@@ -310,0 +312 @@
+response GET /api/tools/qa-contract-summary 200 application/json schema=type="object" additionalProperties=type="string"
```

This confirms the generator surfaces real public REST API additions as concise PR-description diff content. The temporary endpoint was restored afterward.

Test 2: PR body updater insertion and removal

Step 1 — Establish baseline body and empty summary:
Fetched the actual PR body with gh api repos/OpenHands/software-agent-sdk/pulls/3789 --jq .body, then ran the updater with the empty summary produced for the current PR:

body_update_result=changed
second_update_result=unchanged

This revealed the minor newline-normalization issue listed below: visible content was unchanged, but the first local update rewrote line endings.

Step 2 — Apply a generated summary block:
Used the non-empty summary from the temporary real endpoint addition and ran:

python .github/scripts/update_pr_body_with_rest_api_summary.py --body-file /tmp/pr-body-3789.md --summary-file /tmp/rest-api-contract-summary-added-endpoint.md --output /tmp/updated-pr-body-with-summary.md

Observed:

has_start_marker= True
has_end_marker= True
marker_after_summary= True
marker_before_issue_number= True
human_text_present= True

The generated block landed inside ## Summary, before ## Issue Number, and the visible HUMAN text remained present.

Step 3 — Remove stale summary when no contract changes remain:
Ran the updater again with the empty summary file:

has_start_marker= False
has_temp_endpoint_summary= False
summary_bullets_present= True
human_text_present= True

This confirms stale generated blocks are removed while the existing summary bullets and visible HUMAN text remain.

Issues Found

  • 🟡 Minor: Running the updater against the actual PR body with an empty generated summary still changed the output once because CRLF line endings were normalized to LF. This can cause an unnecessary PR-body PATCH even when there is no REST API contract block to add/remove; visible content was preserved.

This QA review was created by an AI agent (OpenHands) on behalf of the user.

Final verdict: PASS WITH ISSUES

Comment thread .github/scripts/update_pr_body_with_rest_api_summary.py Outdated
@openhands-development

Copy link
Copy Markdown

@enyst it looks like you haven't created an OpenHands account yet. Please sign up at OpenHands Cloud and try again.

Co-authored-by: openhands <openhands@all-hands.dev>
@openhands-development

Copy link
Copy Markdown

@enyst it looks like you haven't created an OpenHands account yet. Please sign up at OpenHands Cloud and try again.

enyst and others added 2 commits June 18, 2026 16:30
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-this This label triggers a PR review by OpenHands

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants