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 | ✅ |
openhands.workspace | openhands-workspace | ✅ |
openhands-tools does not yet define __all__ and is not covered by the
automated breakage checks. This is tracked in
#2074.Deprecation Helpers
The SDK provides two canonical ways to mark something as deprecated, both inopenhands.sdk.utils.deprecation:
@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):
DeprecationWarning so users see the message during
development, and record metadata that CI tooling can detect.
Policy 1: Deprecation Before Removal
Any symbol removed from a package’s__all__ 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.
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 deadline enforcement |
- Users always get advance warning before APIs are removed
- Breaking changes are properly versioned
- Deprecated code is eventually cleaned up

