Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@
"sdk/guides/agent-delegation",
"sdk/guides/iterative-refinement",
"sdk/guides/security",
"sdk/guides/deprecation-policy",
"sdk/guides/metrics",
"sdk/guides/observability",
"sdk/guides/secrets",
Expand Down
8 changes: 8 additions & 0 deletions sdk/faq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@

## Does the agent SDK support image content?

**Yes, the OpenHands SDK fully supports image content for vision-capable LLMs.**

Check warning on line 99 in sdk/faq.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/faq.mdx#L99

Did you really mean 'LLMs'?

The SDK supports both HTTP/HTTPS URLs and base64-encoded images through the `ImageContent` class.

Expand Down Expand Up @@ -269,6 +269,14 @@

For the full implementation used in OpenHands benchmarks, see the [fake_user_response.py](https://github.com/OpenHands/benchmarks/blob/main/benchmarks/utils/fake_user_response.py) module.


## How does deprecation work in the SDK?

The SDK defines its public surface via `__all__` and uses the helpers in
[`openhands.sdk.utils.deprecation`](/sdk/api-reference/openhands.sdk.utils) to
emit deprecation warnings and cleanup deadlines. For the full lifecycle, see the
[Deprecation & API Stability](/sdk/guides/deprecation-policy) guide.

## More questions?

If you have additional questions:
Expand Down
163 changes: 163 additions & 0 deletions sdk/guides/deprecation-policy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
---
title: Deprecation & API Stability
description: How the SDK manages deprecations, version bumps, and backward compatibility.
---

The OpenHands SDK enforces a structured deprecation lifecycle to keep the public
API stable while allowing it to evolve. Two CI checks enforce these policies:
`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 |

The curated `__all__` list is the contract that `check_sdk_api_breakage.py` uses
to detect breaking changes.

## Deprecation Helpers

The SDK provides canonical helpers in
[`openhands.sdk.utils.deprecation`](/sdk/api-reference/openhands.sdk.utils) for
both deprecations and cleanup deadlines:

### `@deprecated` decorator

Use on classes and functions that will be removed in a future release:

```python
from openhands.sdk.utils.deprecation import deprecated

@deprecated(deprecated_in="1.10.0", removed_in="1.12.0")
class OldThing:
...
```

### `warn_deprecated()` function

Use for runtime deprecation warnings on dynamic access paths (e.g., property
accessors, conditional branches):

Check warning on line 45 in sdk/guides/deprecation-policy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/guides/deprecation-policy.mdx#L45

Did you really mean 'accessors'?

```python
from openhands.sdk.utils.deprecation import warn_deprecated

class MyModel:
@property
def old_field(self):
warn_deprecated("MyModel.old_field", deprecated_in="1.10.0", removed_in="1.12.0")
return self._new_field
```

### `warn_cleanup()` function

Use for temporary workarounds that must be removed by a specific version or date:

```python
from openhands.sdk.utils.deprecation import warn_cleanup

warn_cleanup(
"Temporary workaround for upstream issue",
cleanup_by="1.16.0",
)
```

`@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.

<Steps>
<Step title="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.
</Step>
<Step title="Release with the deprecation marker">
The deprecation is now recorded in the published package on PyPI.
</Step>
<Step title="Remove the symbol in a subsequent release">
Remove it from `__all__` (and the code). CI will verify the prior release had
the deprecation marker and allow the removal.
</Step>
</Steps>

## 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](https://mkdocstrings.github.io/griffe/), including:
- Removed symbols from `__all__`
- Removed attributes from exported classes
- Changed function signatures

A PATCH bump (e.g., `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

Check warning on line 116 in sdk/guides/deprecation-policy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/guides/deprecation-policy.mdx#L116

Did you really mean 'Pydantic'?
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:

1. **Never use `extra="forbid"` without a deprecation handler** — old events
containing removed fields would fail to deserialize.

Check warning on line 123 in sdk/guides/deprecation-policy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/guides/deprecation-policy.mdx#L123

Did you really mean 'deserialize'?
2. **Add a permanent model validator** using `handle_deprecated_model_fields`:

Check warning on line 124 in sdk/guides/deprecation-policy.mdx

View check run for this annotation

Mintlify / Mintlify Validation (allhandsai) - vale-spellcheck

sdk/guides/deprecation-policy.mdx#L124

Did you really mean 'validator'?

```python
from openhands.sdk.utils.deprecation import handle_deprecated_model_fields

class MyEvent(BaseModel):
model_config = ConfigDict(extra="forbid")

_DEPRECATED_FIELDS: ClassVar[tuple[str, ...]] = ("old_field_name",)

@model_validator(mode="before")
@classmethod
def _handle_deprecated_fields(cls, data: Any) -> Any:
return handle_deprecated_model_fields(data, cls._DEPRECATED_FIELDS)
```

<Warning>
Deprecated field handlers on events are **permanent** and must never be removed.
They ensure old conversations can always be loaded regardless of when they were
created.
</Warning>

## 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