Summary of findings and recommendations
What I reviewed
• Read the agent-sdk README and repo layout:
◦ openhands-sdk: public Python API (exports curated via
all in openhands/sdk/*init*.py)
◦ openhands-agent-server: FastAPI app with OpenAPI generator at openhands-agent-server/openhands/agent_server/openapi.py
• Investigated Griffe (mkdocstrings/griffe) to detect breaking public API changes programmatically.
Proposed release-PR workflow
• Scope: Enforce SemVer correctness for both:
a. Python SDK (programmatic surface)
b. Agent Server (REST surface)
1. SDK (programmatic API) with Griffe
• Load old vs new:
◦ new = griffe.load("openhands.sdk", search_paths=["./openhands-sdk"])
◦ old = griffe.load_git("openhands.sdk", ref=<prev_tag>) or griffe.load_pypi("openhands-sdk", version=<prev>)
• Compare and filter:
◦ Use griffe.find_breaking_changes(old_obj, new_obj)
◦ Respect public API via
all (and/or obj.is_public for additional filtering)
• CI behavior:
◦ Emit PR annotations using ExplanationStyle.GITHUB and a Markdown summary for the job
◦ Fail if any breaking changes are found and the openhands-sdk version in pyproject.toml is not a major bump
2. Server (REST API) via OpenAPI diff
• Generate schemas:
◦ New: SCHEMA_PATH=openapi-new.json python openhands-agent-server/openhands/agent_server/openapi.py
◦ Old: check out previous release (or use a worktree), run the same to produce openapi-old.json
• Diff:
◦ Use an OpenAPI diff tool (e.g., oasdiff) to classify breaking vs non-breaking changes
◦ Fail if breaking changes exist and openhands-agent-server version is not a major bump
◦ Publish a readable summary in the PR
Configuration and flexibility
• Include/exclude configuration (.ci/api-check.yml) to precisely control what’s tracked:
◦ SDK: include_paths (e.g., "openhands.sdk.LLM", "openhands.sdk.Conversation") and exclude_paths
◦ Server: allow_breaking: false, ignore_paths for endpoints you don’t consider public
• If runtime-decorated signatures are used, Griffe supports targeted dynamic inspection through a small extension (only for specific objects) to keep checks accurate and noise-free.
Direct answers to your questions
• Can we select which core classes to track? Yes. You can compare specific classes by path (e.g., openhands.sdk.llm.llm.LLM).
• Can we select methods? Yes. You can compare individual functions/methods by full path (e.g., openhands.sdk.conversation.conversation.Conversation.run).
• Can we select modules? Yes. You can compare entire modules (e.g., openhands.sdk.llm) or the root openhands.sdk.
Key notes
• Griffe uses
all to define public API (which your sdk already exposes), falling back to conventional heuristics when
all is missing.
• WebSockets aren’t covered by OpenAPI; if they’re part of your public surface, consider a separate schema/versioned contract for those events.
• The CI jobs should enforce SemVer per package (SDK vs Agent Server), based on their respective pyproject.toml versions.
If you want, I can implement:
• A GitHub Actions workflow with two jobs (SDK/REST)
• A small scripts/check_sdk_api_breakage.py using Griffe with include/exclude support
• An OpenAPI diff script using oasdiff (or a pure-Python fallback)
Would you like me to create a branch and open a PR, or keep this plan as-is?
<sub>
View full conversation</sub>