check_sdk_api_breakage.py on release PRs and check_deprecations.py on every PR.
Public API Surface
Each published package defines its public API via__all__ in its top-level
__init__.py. The following packages are currently covered:
| Package | Distribution | __all__ |
|---|---|---|
openhands.sdk | openhands-sdk | Yes |
openhands.workspace | openhands-workspace | Yes |
openhands.tools | openhands-tools | Yes |
__all__ list is the contract that check_sdk_api_breakage.py uses
to detect breaking changes.
Deprecation Helpers
The SDK provides canonical helpers inopenhands.sdk.utils.deprecation for
both deprecations and cleanup deadlines:
@deprecated decorator
Use on classes and functions that will be removed in a future release:
warn_deprecated() function
Use for runtime deprecation warnings on dynamic access paths (e.g., property
accessors, conditional branches):
warn_cleanup() function
Use for temporary workarounds that must be removed by a specific version or date:
@deprecated and warn_deprecated() emit deprecation warnings and record
metadata that CI tooling can detect. warn_cleanup() emits a UserWarning once
its cleanup deadline is reached and is enforced by check_deprecations.py.
Policy 1: Deprecation Before Removal
Any symbol removed from a package’s__all__, or any public member removed
from an exported class, must have been marked as deprecated for at least one
release before removal.
This is enforced by check_sdk_api_breakage.py, which AST-scans the
previous PyPI release looking for @deprecated decorators or
warn_deprecated() calls. If a removed symbol was never deprecated,
CI flags it as an error. Deprecating a class counts as deprecating its
public members for the purposes of member removal.
Mark the symbol as deprecated
Add
@deprecated(...) or warn_deprecated(...) in the current release.
The symbol stays in __all__ and continues to work — users just see a warning.Release with the deprecation marker
The deprecation is now recorded in the published package on PyPI.
Policy 2: MINOR Version Bump for Breaking Changes
Any breaking change — removal of an exported symbol or structural change to a public class/function — requires at least a MINOR version bump (e.g.,1.11.x → 1.12.0).
This applies to all structural breakages detected by
Griffe, including:
- Removed symbols from
__all__ - Removed attributes from exported classes
- Changed function signatures
1.11.3 → 1.11.4) with breaking changes will fail CI.
Event Field Deprecation (Special Case)
Event types (Pydantic models used in event serialization) have an additional constraint: old events must always load without error, because production systems may resume conversations containing events from older SDK versions. When removing a field from an event type:- Never use
extra="forbid"without a deprecation handler — old events containing removed fields would fail to deserialize. - Add a permanent model validator using
handle_deprecated_model_fields:
CI Checks
Two scripts enforce these policies automatically:| Script | Runs on | What it checks |
|---|---|---|
check_sdk_api_breakage.py | Release PRs (rel-* branches) | Deprecation-before-removal + MINOR bump |
check_deprecations.py | Every PR | Deprecation + cleanup deadline enforcement |
check_deprecations.py scans openhands-sdk, openhands-tools,
openhands-workspace, and openhands-agent-server for expired removed_in
or cleanup_by deadlines.
Together they ensure that:
- Users always get advance warning before APIs are removed
- Breaking changes are properly versioned
- Deprecated code is eventually cleaned up

